From a85b09a8a4abf85b83b569d15d596de804b30ccb Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 20 Nov 2019 20:44:20 -0500 Subject: [PATCH 1/6] refactor: move module name helper into separate file --- lib/rules/new-module-imports.js | 2 +- lib/rules/no-classic-classes.js | 2 +- lib/rules/require-tagless-components.js | 2 +- lib/rules/use-ember-data-rfc-395-imports.js | 2 +- lib/utils/ember.js | 3 +- lib/utils/import-info.js | 29 +++++++++++++++++++ lib/utils/utils.js | 23 --------------- ...-source-module-name-for-identifier-test.js | 2 +- 8 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 lib/utils/import-info.js rename tests/lib/utils/{utils => import-info}/get-source-module-name-for-identifier-test.js (98%) diff --git a/lib/rules/new-module-imports.js b/lib/rules/new-module-imports.js index 017783ced2..a94d2d467b 100644 --- a/lib/rules/new-module-imports.js +++ b/lib/rules/new-module-imports.js @@ -2,7 +2,7 @@ const MAPPING = require('ember-rfc176-data'); const { buildMessage, getFullNames, isDestructuring } = require('../utils/new-module'); -const { getSourceModuleNameForIdentifier } = require('../utils/utils'); +const { getSourceModuleNameForIdentifier } = require('../utils/import-info'); const GLOBALS = MAPPING.reduce((memo, exportDefinition) => { if (exportDefinition.deprecated) { diff --git a/lib/rules/no-classic-classes.js b/lib/rules/no-classic-classes.js index 6be70cc511..cfb641a435 100644 --- a/lib/rules/no-classic-classes.js +++ b/lib/rules/no-classic-classes.js @@ -1,6 +1,6 @@ 'use strict'; -const { getSourceModuleNameForIdentifier } = require('../utils/utils'); +const { getSourceModuleNameForIdentifier } = require('../utils/import-info'); const { isObjectExpression } = require('../utils/types'); const ERROR_MESSAGE_NO_CLASSIC_CLASSES = diff --git a/lib/rules/require-tagless-components.js b/lib/rules/require-tagless-components.js index 4dc7847135..8103cb4e7c 100644 --- a/lib/rules/require-tagless-components.js +++ b/lib/rules/require-tagless-components.js @@ -1,7 +1,7 @@ 'use strict'; const { isEmberComponent } = require('../utils/ember'); -const { getSourceModuleNameForIdentifier } = require('../utils/utils'); +const { getSourceModuleNameForIdentifier } = require('../utils/import-info'); const { isCallExpression, isIdentifier, diff --git a/lib/rules/use-ember-data-rfc-395-imports.js b/lib/rules/use-ember-data-rfc-395-imports.js index 3d033cf5a9..6e23640c97 100644 --- a/lib/rules/use-ember-data-rfc-395-imports.js +++ b/lib/rules/use-ember-data-rfc-395-imports.js @@ -2,7 +2,7 @@ const MAPPINGS = require('@ember-data/rfc395-data'); const { buildFix, buildMessage, getFullNames, isDestructuring } = require('../utils/new-module'); -const { getSourceModuleNameForIdentifier } = require('../utils/utils'); +const { getSourceModuleNameForIdentifier } = require('../utils/import-info'); /** * This function returns an object like this: diff --git a/lib/utils/ember.js b/lib/utils/ember.js index c417c389d1..b85ac09819 100644 --- a/lib/utils/ember.js +++ b/lib/utils/ember.js @@ -1,5 +1,6 @@ 'use strict'; +const { getSourceModuleNameForIdentifier } = require('./import-info'); const utils = require('./utils'); const types = require('./types'); const assert = require('assert'); @@ -177,7 +178,7 @@ function isEmberCoreModule(context, node, moduleName) { if (!node.superClass) { return false; } - const superClassImportPath = utils.getSourceModuleNameForIdentifier(context, node.superClass); + const superClassImportPath = getSourceModuleNameForIdentifier(context, node.superClass); if (superClassImportPath === CORE_MODULE_IMPORT_PATHS[moduleName]) { return true; diff --git a/lib/utils/import-info.js b/lib/utils/import-info.js new file mode 100644 index 0000000000..5ed8289877 --- /dev/null +++ b/lib/utils/import-info.js @@ -0,0 +1,29 @@ +'use strict'; + +const { isImportDeclaration } = require('./types'); +const { getSourceModuleName } = require('./utils'); + +module.exports = { + getSourceModuleNameForIdentifier, +}; + +/** + * Gets the name of the module that an identifier was imported from, + * if it was imported + * + * @param {Object} context the context of the ESLint rule + * @param {node} node the Idenfifier to find an import for + * @returns {string | undefined} The name of the module the identifier was imported from, if it was imported + */ +function getSourceModuleNameForIdentifier(context, node) { + const [program] = context.getAncestors(node); + const importDeclaration = program.body + .filter(isImportDeclaration) + .find(importDeclaration => + importDeclaration.specifiers.some( + specifier => specifier.local.name === getSourceModuleName(node) + ) + ); + + return importDeclaration ? importDeclaration.source.value : undefined; +} diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 10aecd985f..8f4831dba4 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -3,7 +3,6 @@ const { isCallExpression, isIdentifier, - isImportDeclaration, isLiteral, isMemberExpression, isNewExpression, @@ -19,7 +18,6 @@ module.exports = { getPropertyValue, getSize, getSourceModuleName, - getSourceModuleNameForIdentifier, isEmptyMethod, isGlobalCallExpression, parseArgs, @@ -202,27 +200,6 @@ function isEmptyMethod(node) { return node.value.body && node.value.body.body && node.value.body.body.length <= 0; } -/** - * Gets the name of the module that an identifier was imported from, - * if it was imported - * - * @param {Object} context the context of the ESLint rule - * @param {node} node the Idenfifier to find an import for - * @returns {string | undefined} The name of the module the identifier was imported from, if it was imported - */ -function getSourceModuleNameForIdentifier(context, node) { - const [program] = context.getAncestors(node); - const importDeclaration = program.body - .filter(isImportDeclaration) - .find(importDeclaration => - importDeclaration.specifiers.some( - specifier => specifier.local.name === getSourceModuleName(node) - ) - ); - - return importDeclaration ? importDeclaration.source.value : undefined; -} - function getSourceModuleName(node) { if (isMemberExpression(node) && node.object) { return getSourceModuleName(node.object); diff --git a/tests/lib/utils/utils/get-source-module-name-for-identifier-test.js b/tests/lib/utils/import-info/get-source-module-name-for-identifier-test.js similarity index 98% rename from tests/lib/utils/utils/get-source-module-name-for-identifier-test.js rename to tests/lib/utils/import-info/get-source-module-name-for-identifier-test.js index b8fc03b9cc..250b6fe2a8 100644 --- a/tests/lib/utils/utils/get-source-module-name-for-identifier-test.js +++ b/tests/lib/utils/import-info/get-source-module-name-for-identifier-test.js @@ -1,4 +1,4 @@ -const { getSourceModuleNameForIdentifier } = require('../../../../lib/utils/utils'); +const { getSourceModuleNameForIdentifier } = require('../../../../lib/utils/import-info'); const { FauxContext } = require('../../../helpers/faux-context'); const babelEslint = require('babel-eslint'); From d788c25214a60fcac30d3e09e16ae08f4d5df363 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 20 Nov 2019 20:47:45 -0500 Subject: [PATCH 2/6] refactor: extract utility for getting import node --- lib/utils/import-info.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/utils/import-info.js b/lib/utils/import-info.js index 5ed8289877..b0d97cb28a 100644 --- a/lib/utils/import-info.js +++ b/lib/utils/import-info.js @@ -8,22 +8,35 @@ module.exports = { }; /** - * Gets the name of the module that an identifier was imported from, + * Gets the ImportDeclaration node that an identifier was imported from * if it was imported * * @param {Object} context the context of the ESLint rule * @param {node} node the Idenfifier to find an import for - * @returns {string | undefined} The name of the module the identifier was imported from, if it was imported + * @returns {ImportDeclaration | undefined} The ImportDeclaration of the module the identifier was imported from, if it was imported */ -function getSourceModuleNameForIdentifier(context, node) { +function getImportDeclarationNode(context, node) { const [program] = context.getAncestors(node); - const importDeclaration = program.body + + return program.body .filter(isImportDeclaration) .find(importDeclaration => importDeclaration.specifiers.some( specifier => specifier.local.name === getSourceModuleName(node) ) ); +} + +/** + * Gets the name of the module that an identifier was imported from, + * if it was imported + * + * @param {Object} context the context of the ESLint rule + * @param {node} node the Idenfifier to find an import for + * @returns {string | undefined} The name of the module the identifier was imported from, if it was imported + */ +function getSourceModuleNameForIdentifier(context, node) { + const importDeclaration = getImportDeclarationNode(context, node); return importDeclaration ? importDeclaration.source.value : undefined; } From d929f5ea5d74fb3af8da7f6f4ba0eb6f5115062a Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 20 Nov 2019 21:11:55 -0500 Subject: [PATCH 3/6] feat: add helper for checking if an Identifier is a default export --- lib/utils/import-info.js | 26 +++++++- lib/utils/types.js | 11 ++++ .../utils/import-info/is-default-import.js | 63 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 tests/lib/utils/import-info/is-default-import.js diff --git a/lib/utils/import-info.js b/lib/utils/import-info.js index b0d97cb28a..1f7daacec6 100644 --- a/lib/utils/import-info.js +++ b/lib/utils/import-info.js @@ -1,10 +1,11 @@ 'use strict'; -const { isImportDeclaration } = require('./types'); +const { isImportDeclaration, isImportDefaultSpecifier } = require('./types'); const { getSourceModuleName } = require('./utils'); module.exports = { getSourceModuleNameForIdentifier, + isDefaultImport, }; /** @@ -27,6 +28,16 @@ function getImportDeclarationNode(context, node) { ); } +function getImportSpecifierNode(context, node) { + const importDeclaration = getImportDeclarationNode(context, node); + + return importDeclaration + ? importDeclaration.specifiers.find( + specifier => specifier.local.name === getSourceModuleName(node) + ) + : undefined; +} + /** * Gets the name of the module that an identifier was imported from, * if it was imported @@ -40,3 +51,16 @@ function getSourceModuleNameForIdentifier(context, node) { return importDeclaration ? importDeclaration.source.value : undefined; } + +/** + * Determines whether the given Identifer was the default import from some module + * + * @param {Object} context the context of the ESLint rule + * @param {node} node the Idenfifier to check + * @returns {boolean} Whether or not the identifier is a default import from a module + */ +function isDefaultImport(context, node) { + const importSpecifier = getImportSpecifierNode(context, node); + + return isImportDefaultSpecifier(importSpecifier); +} diff --git a/lib/utils/types.js b/lib/utils/types.js index 30886530e1..07c31e14ba 100644 --- a/lib/utils/types.js +++ b/lib/utils/types.js @@ -18,6 +18,7 @@ module.exports = { isFunctionExpression, isIdentifier, isImportDeclaration, + isImportDefaultSpecifier, isLiteral, isLogicalExpression, isMemberExpression, @@ -225,6 +226,16 @@ function isImportDeclaration(node) { return node !== undefined && node.type === 'ImportDeclaration'; } +/** + * Check whether or not a node is an ImportDefaultSpecifier. + * + * @param {Object} node The node to check. + * @returns {boolean} Whether or not the node is an ImportDefaultSpecifier. + */ +function isImportDefaultSpecifier(node) { + return node !== undefined && node.type === 'ImportDefaultSpecifier'; +} + /** * Check whether or not a node is an Literal. * diff --git a/tests/lib/utils/import-info/is-default-import.js b/tests/lib/utils/import-info/is-default-import.js new file mode 100644 index 0000000000..aaf07ab981 --- /dev/null +++ b/tests/lib/utils/import-info/is-default-import.js @@ -0,0 +1,63 @@ +const { isDefaultImport } = require('../../../../lib/utils/import-info'); +const { FauxContext } = require('../../../helpers/faux-context'); +const babelEslint = require('babel-eslint'); + +test('when the identifier is not imported', () => { + const context = new FauxContext(` + Foo; + `); + + const node = { name: 'Foo', type: 'Identifier' }; + + expect(isDefaultImport(context, node)).toEqual(false); +}); + +describe('when the identifier is imported', () => { + test('as a default export', () => { + const context = new FauxContext(` + import Foo from 'bar'; + + Foo; + `); + + const node = { name: 'Foo', type: 'Identifier' }; + + expect(isDefaultImport(context, node)).toEqual(true); + }); + + test('as a named export', () => { + const context = new FauxContext(` + import { Foo } from 'bar'; + + Foo; + `); + + const node = { name: 'Foo', type: 'Identifier' }; + + expect(isDefaultImport(context, node)).toEqual(false); + }); + + test('when aliasing a named export', () => { + const context = new FauxContext(` + import { SomeOtherThing as Foo } from 'bar'; + + Foo; + `); + + const node = { name: 'Foo', type: 'Identifier' }; + + expect(isDefaultImport(context, node)).toEqual(false); + }); + + test('Some.Long.Chained.Path.extend', () => { + const context = new FauxContext(` + import Some from 'some-path'; + + Some.Long.Chained.Path.extend(); + `); + + const node = babelEslint.parse('Some.Long.Chained.Path.extend({})').body[0].expression.callee; + + expect(isDefaultImport(context, node)).toEqual(true); + }); +}); From 39e3036ab4e18d5c4398a1e9117b6210ba2c8465 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 20 Nov 2019 21:56:51 -0500 Subject: [PATCH 4/6] chore: update test cases to include imports --- tests/lib/rules/alias-model-in-controller.js | 15 +- .../rules/avoid-using-needs-in-controllers.js | 70 +- tests/lib/rules/no-actions-hash.js | 10 + tests/lib/rules/no-new-mixins.js | 11 +- tests/lib/rules/no-on-calls-in-components.js | 195 ++++- tests/lib/rules/order-in-components.js | 744 +++++++++++------- tests/lib/rules/order-in-controllers.js | 366 +++++---- tests/lib/rules/order-in-routes.js | 658 +++++++++------- tests/lib/rules/require-super-in-init.js | 617 ++++++++------- tests/lib/utils/ember-test.js | 112 ++- 10 files changed, 1685 insertions(+), 1113 deletions(-) diff --git a/tests/lib/rules/alias-model-in-controller.js b/tests/lib/rules/alias-model-in-controller.js index c139632b2e..6eb5a49526 100644 --- a/tests/lib/rules/alias-model-in-controller.js +++ b/tests/lib/rules/alias-model-in-controller.js @@ -108,7 +108,10 @@ eslintTester.run('alias-model-in-controller', rule, { }, { filename: 'example-app/controllers/path/to/some-feature.js', - code: 'export default CustomController.extend({});', + code: ` + import CustomController from '@ember/controller'; + export default CustomController.extend({}); + `, output: null, errors: [ { @@ -118,7 +121,10 @@ eslintTester.run('alias-model-in-controller', rule, { }, { filename: 'example-app/some-feature/controller.js', - code: 'export default CustomController.extend({});', + code: ` + import CustomController from '@ember/controller'; + export default CustomController.extend({}); + `, output: null, errors: [ { @@ -128,7 +134,10 @@ eslintTester.run('alias-model-in-controller', rule, { }, { filename: 'example-app/twisted-path/some-file.js', - code: 'export default Controller.extend({});', + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({}); + `, output: null, errors: [ { diff --git a/tests/lib/rules/avoid-using-needs-in-controllers.js b/tests/lib/rules/avoid-using-needs-in-controllers.js index 1ffc98db0a..52a4e59673 100644 --- a/tests/lib/rules/avoid-using-needs-in-controllers.js +++ b/tests/lib/rules/avoid-using-needs-in-controllers.js @@ -17,16 +17,37 @@ const eslintTester = new RuleTester({ }); eslintTester.run('avoid-using-needs-in-controllers', rule, { valid: [ - 'export default Controller.extend();', - 'export default FooController.extend();', - 'Controller.reopen();', - 'FooController.reopen();', - 'Controller.reopenClass();', - 'FooController.reopenClass();', + ` + import Controller from '@ember/controller'; + export default Controller.extend(); + `, + ` + import FooController from '@ember/controller'; + export default FooController.extend(); + `, + ` + import Controller from '@ember/controller'; + Controller.reopen(); + `, + ` + import FooController from '@ember/controller'; + FooController.reopen(); + `, + ` + import Controller from '@ember/controller'; + Controller.reopenClass(); + `, + ` + import FooController from '@ember/controller'; + FooController.reopenClass(); + `, ], invalid: [ { - code: 'export default Controller.extend({ needs: [] });', + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ needs: [] }); + `, output: null, errors: [ { @@ -36,7 +57,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { ], }, { - code: 'Controller.reopenClass({ needs: [] });', + code: ` + import Controller from '@ember/controller'; + Controller.reopenClass({ needs: [] }); + `, output: null, errors: [ { @@ -46,7 +70,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { ], }, { - code: 'Controller.reopen({ needs: [] });', + code: ` + import Controller from '@ember/controller'; + Controller.reopen({ needs: [] }); + `, output: null, errors: [ { @@ -56,7 +83,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { ], }, { - code: "export default Controller['extend']({ needs: [] });", + code: ` + import Controller from '@ember/controller'; + export default Controller['extend']({ needs: [] }); + `, output: null, errors: [ { @@ -67,7 +97,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { }, { filename: 'example-app/controllers/some-controller.js', - code: 'export default FooController.extend({ needs: [] });', + code: ` + import FooController from '@ember/controller'; + export default FooController.extend({ needs: [] }); + `, output: null, errors: [ { @@ -78,7 +111,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { }, { filename: 'example-app/controllers/some-controller.js', - code: 'FooController.reopenClass({ needs: [] });', + code: ` + import FooController from '@ember/controller'; + FooController.reopenClass({ needs: [] }); + `, output: null, errors: [ { @@ -89,7 +125,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { }, { filename: 'example-app/controllers/some-controller.js', - code: 'FooController.reopen({ needs: [] });', + code: ` + import FooController from '@ember/controller'; + FooController.reopen({ needs: [] }); + `, output: null, errors: [ { @@ -100,7 +139,10 @@ eslintTester.run('avoid-using-needs-in-controllers', rule, { }, { filename: 'example-app/controllers/some-controller.js', - code: "export default FooController['extend']({ needs: [] });", + code: ` + import FooController from '@ember/controller'; + export default FooController['extend']({ needs: [] }); + `, output: null, errors: [ { diff --git a/tests/lib/rules/no-actions-hash.js b/tests/lib/rules/no-actions-hash.js index eece3e3c52..1b4c743eda 100644 --- a/tests/lib/rules/no-actions-hash.js +++ b/tests/lib/rules/no-actions-hash.js @@ -18,33 +18,39 @@ const ruleTester = new RuleTester({ ruleTester.run('no-actions-hash', rule, { valid: [ ` + import Component from '@ember/component'; export default Component.extend({ foo: action(function() {}) }); `, ` + import Component from '@ember/component'; export default class MyComponent extends Component { @action foo() {} } `, ` + import Controller from '@ember/controller'; export default Controller.extend({ foo: action(function() {}) }); `, ` + import Controller from '@ember/controller'; export default class MyController extends Controller { @action foo() {} } `, ` + import Route from '@ember/routing/route'; export default Route.extend({ foo: action(function() {}) }); `, ` + import Route from '@ember/routing/route'; export default class MyRoute extends Route { @action foo() {} @@ -59,6 +65,7 @@ ruleTester.run('no-actions-hash', rule, { } `, ` + import Service from '@ember/service'; export default Service.extend({ actions: { }, @@ -69,6 +76,7 @@ ruleTester.run('no-actions-hash', rule, { invalid: [ { code: ` + import Component from '@ember/component'; export default Component.extend({ actions: { }, @@ -90,6 +98,7 @@ ruleTester.run('no-actions-hash', rule, { }, { code: ` + import Controller from '@ember/controller'; export default Controller.extend({ actions: { }, @@ -111,6 +120,7 @@ ruleTester.run('no-actions-hash', rule, { }, { code: ` + import Route from '@ember/routing/route'; export default Route.extend({ actions: { }, diff --git a/tests/lib/rules/no-new-mixins.js b/tests/lib/rules/no-new-mixins.js index 9f9b69c12a..00cffb2b3a 100644 --- a/tests/lib/rules/no-new-mixins.js +++ b/tests/lib/rules/no-new-mixins.js @@ -16,10 +16,13 @@ const eslintTester = new RuleTester({ eslintTester.run('no-new-mixins', rule, { valid: [ ` - import mixin from "some/addon"; - export default mixin; - `, - 'export default mixin.create({actions: {},});', + import mixin from "some/addon"; + export default mixin; + `, + ` + import mixin from 'some/addon'; + export default mixin.create(); + `, ], invalid: [ { diff --git a/tests/lib/rules/no-on-calls-in-components.js b/tests/lib/rules/no-on-calls-in-components.js index 29464f3fd2..08b07d0ca0 100644 --- a/tests/lib/rules/no-on-calls-in-components.js +++ b/tests/lib/rules/no-on-calls-in-components.js @@ -17,66 +17,170 @@ const message = "Don't use .on() for component lifecycle events."; eslintTester.run('no-on-calls-in-components', rule, { valid: [ - 'export default Component.extend();', - 'export default Component.extend({actions: {}});', - 'export default Component.extend({abc: service()});', - 'export default Component.extend({abc: inject.service()});', - 'export default Component.extend({abc: false});', - 'export default Component.extend({classNames: ["abc", "def"]});', - 'export default Component.extend({abc: computed(function () {})});', - 'export default Component.extend({abc: observer("abc", function () {})});', - 'export default Component.extend({abc: observer("abc", function () {test.on("xyz", def)})});', - 'export default Component.extend({abc: function () {test.on("xyz", def)}});', - 'export default Component.extend({abc() {$("body").on("click", def)}});', - 'export default Component.extend({didInsertElement() {$("body").on("click", def).on("click", function () {})}});', - 'export default Component.extend({actions: {abc() {test.on("xyz", def)}}});', - 'export default Component.extend({actions: {abc() {$("body").on("click", def).on("click", function () {})}}});', - 'export default Component.extend({abc: on("nonLifecycleEvent", function() {})});', + ` + import Component from '@ember/component'; + export default Component.extend(); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + actions: {} + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: service() + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: inject.service() + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: false + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + classNames: ["abc", "def"] + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: computed(function () {}) + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: observer("abc", function () {}) + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: observer("abc", function () { + test.on("xyz", def) + }) + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: function () { + test.on("xyz", def) + } + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc() { + $("body").on("click", def) + } + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + didInsertElement() { + $("body").on("click", def).on("click", function () {}) + } + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + actions: { + abc() { + test.on("xyz", def) + } + } + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + actions: { + abc() { + $("body").on("click", def).on("click", function () {}) + } + } + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ + abc: on("nonLifecycleEvent", function() {}) + }); + `, { code: ` - let foo = { bar: 'baz' }; + import Component from '@ember/component'; - export default Component.extend({ - ...foo, - }); + let foo = { bar: 'baz' }; + + export default Component.extend({ + ...foo, + }); `, parserOptions: { ecmaVersion: 9, sourceType: 'module' }, }, ], invalid: [ { - code: `export default Component.extend({ - test: on("didInsertElement", function () {}) - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + test: on("didInsertElement", function () {}) + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component.extend({ - test: on("init", observer("someProperty", function () { - return true; - })), - someComputedProperty: computed.bool(true) - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + test: on("init", observer("someProperty", function () { + return true; + })), + someComputedProperty: computed.bool(true) + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component.extend({ - test: Ember.on("didInsertElement", function () {}), - someComputedProperty: Ember.computed.readOnly('Hello World!'), - anotherTest: Ember.on("willDestroyElement", function () {}) - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + test: Ember.on("didInsertElement", function () {}), + someComputedProperty: Ember.computed.readOnly('Hello World!'), + anotherTest: Ember.on("willDestroyElement", function () {}) + }); + `, output: null, errors: [ - { message, line: 2 }, { message, line: 4 }, + { message, line: 6 }, ], }, { filename: 'example-app/components/some-component/component.js', - code: - 'export default CustomComponent.extend({test: on("didInsertElement", function () {})});', + code: ` + import CustomComponent from '@ember/component'; + export default CustomComponent.extend({ + test: on("didInsertElement", function () {}) + }); + `, output: null, errors: [ { @@ -86,8 +190,12 @@ eslintTester.run('no-on-calls-in-components', rule, { }, { filename: 'example-app/components/some-component.js', - code: - 'export default CustomComponent.extend({test: on("didInsertElement", function () {})});', + code: ` + import CustomComponent from '@ember/component'; + export default CustomComponent.extend({ + test: on("didInsertElement", function () {}) + }); + `, output: null, errors: [ { @@ -97,7 +205,12 @@ eslintTester.run('no-on-calls-in-components', rule, { }, { filename: 'example-app/twised-path/some-file.js', - code: 'export default Component.extend({test: on("didInsertElement", function () {})});', + code: ` + import Component from '@ember/component'; + export default Component.extend({ + test: on("didInsertElement", function () {}) + }); + `, output: null, errors: [ { diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js index a7997bf7c5..005bd3533f 100644 --- a/tests/lib/rules/order-in-components.js +++ b/tests/lib/rules/order-in-components.js @@ -17,8 +17,13 @@ const eslintTester = new RuleTester({ eslintTester.run('order-in-components', rule, { valid: [ - 'export default Component.extend();', - `export default Component.extend({ + ` + import Component from '@ember/component'; + export default Component.extend(); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", vehicle: alias("car"), @@ -27,34 +32,49 @@ eslintTester.run('order-in-components', rule, { }), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", levelOfHappiness: computed("attitude", "health", () => { }), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ levelOfHappiness: computed("attitude", "health", () => { }), actions: {} - });`, - `export default Component.extend(TestMixin, { + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend(TestMixin, { levelOfHappiness: computed("attitude", "health", () => { }), actions: {} - });`, - `export default Component.extend(TestMixin, TestMixin2, { + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend(TestMixin, TestMixin2, { levelOfHappiness: computed("attitude", "health", () => { }), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ abc: Ember.inject.service(), def: inject.service(), ghi: service(), @@ -63,28 +83,35 @@ eslintTester.run('order-in-components', rule, { levelOfHappiness: computed("attitude", "health", () => { }) - });`, + }); + `, ` - import { inject } from '@ember/service'; - export default Component.extend({ - abc: inject(), - def: inject.service(), - ghi: service(), + import Component from '@ember/component'; + import { inject } from '@ember/service'; + export default Component.extend({ + abc: inject(), + def: inject.service(), + ghi: service(), - role: "sloth", + role: "sloth", - levelOfHappiness: computed("attitude", "health", () => { - }) - }); - `, - `export default Component.extend({ + levelOfHappiness: computed("attitude", "health", () => { + }) + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", abc: [], def: {}, ghi: alias("def") - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ levelOfHappiness: computed("attitude", "health", () => { }), @@ -95,8 +122,11 @@ eslintTester.run('order-in-components', rule, { }), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ abc: observer("aaaa", () => { }), @@ -108,8 +138,11 @@ eslintTester.run('order-in-components', rule, { customFunc() { return true; } - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ igh: service(), abc: [], @@ -131,8 +164,11 @@ eslintTester.run('order-in-components', rule, { customFunc() { return true; } - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ init() { }, didReceiveAttrs() { @@ -159,8 +195,11 @@ eslintTester.run('order-in-components', rule, { }, actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ test: service(), didReceiveAttrs() { @@ -168,8 +207,11 @@ eslintTester.run('order-in-components', rule, { tSomeAction: task(function* (url) { }) - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ test: service(), test2: computed.equal("asd", "qwe"), @@ -179,8 +221,11 @@ eslintTester.run('order-in-components', rule, { tSomeAction: task(function* (url) { }).restartable() - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ test: service(), someEmptyMethod() {}, @@ -194,8 +239,11 @@ eslintTester.run('order-in-components', rule, { _anotherPrivateFnc() { return true; } - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ classNameBindings: ["filterDateSelectClass"], content: [], currentMonthEndDate: null, @@ -204,41 +252,54 @@ eslintTester.run('order-in-components', rule, { optionLabelPath: "label", typeOfDate: null, action: K - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", levelOfHappiness: computed.or("asd", "qwe"), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", levelOfHappiness: computed(function() {}), actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", levelOfHappiness: computed(function() { }), actions: {} - });`, + }); + `, { - code: `export default Component.extend({ - role: "sloth", + code: ` + import Component from '@ember/component'; + export default Component.extend({ + role: "sloth", - computed1: computed(function() { - }), - computed2: alias('computed1'), + computed1: computed(function() { + }), + computed2: alias('computed1'), - actions: {}, + actions: {}, - foobar: Ember.inject.service(), - });`, + foobar: Ember.inject.service(), + }); + `, options: [ { order: ['property', 'multi-line-function', 'single-line-function', 'actions'], @@ -246,64 +307,85 @@ eslintTester.run('order-in-components', rule, { ], }, { - code: `export default Component.extend({ - role: "sloth", + code: ` + import Component from '@ember/component'; + export default Component.extend({ + role: "sloth", - computed1: alias('computed2'), - computed2: computed(function() { - }), - computed3: alias('computed1'), + computed1: alias('computed2'), + computed2: computed(function() { + }), + computed3: alias('computed1'), - actions: {}, + actions: {}, - foobar: Ember.inject.service(), - });`, + foobar: Ember.inject.service(), + }); + `, options: [ { order: ['property', ['single-line-function', 'multi-line-function'], 'actions'], }, ], }, - `export default Component.extend({ + ` + import Component from '@ember/component'; + export default Component.extend({ role: "sloth", qwe: foo ? 'bar' : null, abc: [], def: {}, ghi: alias("def") - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ template: hbs\`Hello world {{name}}\`, name: "Jon Snow", actions: {} - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ layout, tabindex: -1, someComputedValue: computed.reads('count'), - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ foo: computed(function() { }).volatile(), bar: computed(function() { }) - });`, - `export default Component.extend({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend({ onFoo() {}, onFoo: () => {}, foo: computed(function() { }).volatile(), bar() { const foo = 'bar'} - });`, + }); + `, { - code: `export default Component.extend({ - onFoo() {}, - onFoo: () => {}, - foo: computed(function() { - }).volatile(), - bar() { const foo = 'bar'} - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + onFoo() {}, + onFoo: () => {}, + foo: computed(function() { + }).volatile(), + bar() { const foo = 'bar'} + }); + `, options: [ { order: [ @@ -319,150 +401,172 @@ eslintTester.run('order-in-components', rule, { ], invalid: [ { - code: `export default Component.extend({ - actions: {}, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + actions: {}, - role: "sloth", + role: "sloth", - vehicle: alias("car"), + vehicle: alias("car"), - levelOfHappiness: computed("attitude", "health", () => { - }) - });`, + levelOfHappiness: computed("attitude", "health", () => { + }) + }); + `, errors: [ { - message: 'The "role" property should be above the actions hash on line 2', - line: 4, + message: 'The "role" property should be above the actions hash on line 4', + line: 6, }, { - message: 'The "vehicle" single-line function should be above the actions hash on line 2', - line: 6, + message: 'The "vehicle" single-line function should be above the actions hash on line 4', + line: 8, }, { message: - 'The "levelOfHappiness" multi-line function should be above the actions hash on line 2', - line: 8, + 'The "levelOfHappiness" multi-line function should be above the actions hash on line 4', + line: 10, }, ], }, { - code: `export default Component.extend({ - vehicle: alias("car"), + code: ` + import Component from '@ember/component'; + export default Component.extend({ + vehicle: alias("car"), - role: "sloth", + role: "sloth", - levelOfHappiness: computed("attitude", "health", () => { - }), + levelOfHappiness: computed("attitude", "health", () => { + }), - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "role" property should be above the "vehicle" single-line function on line 2', - line: 4, + 'The "role" property should be above the "vehicle" single-line function on line 4', + line: 6, }, ], }, { - code: `export default Component.extend({ - levelOfHappiness: computed("attitude", "health", () => { - }), + code: ` + import Component from '@ember/component'; + export default Component.extend({ + levelOfHappiness: computed("attitude", "health", () => { + }), - vehicle: alias("car"), + vehicle: alias("car"), - role: "sloth", + role: "sloth", - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 2', - line: 5, + 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 4', + line: 7, }, { message: - 'The "role" property should be above the "levelOfHappiness" multi-line function on line 2', - line: 7, + 'The "role" property should be above the "levelOfHappiness" multi-line function on line 4', + line: 9, }, ], }, { - code: `export default Component.extend(TestMixin, { - levelOfHappiness: computed("attitude", "health", () => { - }), + code: ` + import Component from '@ember/component'; + export default Component.extend(TestMixin, { + levelOfHappiness: computed("attitude", "health", () => { + }), - vehicle: alias("car"), + vehicle: alias("car"), - role: "sloth", + role: "sloth", - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 2', - line: 5, + 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 4', + line: 7, }, { message: - 'The "role" property should be above the "levelOfHappiness" multi-line function on line 2', - line: 7, + 'The "role" property should be above the "levelOfHappiness" multi-line function on line 4', + line: 9, }, ], }, { - code: `export default Component.extend(TestMixin, TestMixin2, { - levelOfHappiness: computed("attitude", "health", () => { - }), + code: ` + import Component from '@ember/component'; + export default Component.extend(TestMixin, TestMixin2, { + levelOfHappiness: computed("attitude", "health", () => { + }), - vehicle: alias("car"), + vehicle: alias("car"), - role: "sloth", + role: "sloth", - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 2', - line: 5, + 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 4', + line: 7, }, { message: - 'The "role" property should be above the "levelOfHappiness" multi-line function on line 2', - line: 7, + 'The "role" property should be above the "levelOfHappiness" multi-line function on line 4', + line: 9, }, ], }, { - code: `export default Component.extend({ - abc: true, - i18n: service() - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + abc: true, + i18n: service() + }); + `, errors: [ { - message: 'The "i18n" service injection should be above the "abc" property on line 2', - line: 3, + message: 'The "i18n" service injection should be above the "abc" property on line 4', + line: 5, }, ], }, { - code: `export default Component.extend({ - vehicle: alias("car"), - i18n: service() - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + vehicle: alias("car"), + i18n: service() + }); + `, errors: [ { message: - 'The "i18n" service injection should be above the "vehicle" single-line function on line 2', - line: 3, + 'The "i18n" service injection should be above the "vehicle" single-line function on line 4', + line: 5, }, ], }, { code: ` + import Component from '@ember/component'; import { inject } from '@ember/service'; export default Component.extend({ vehicle: alias("car"), @@ -472,287 +576,341 @@ eslintTester.run('order-in-components', rule, { errors: [ { message: - 'The "i18n" service injection should be above the "vehicle" single-line function on line 4', - line: 5, + 'The "i18n" service injection should be above the "vehicle" single-line function on line 5', + line: 6, }, ], }, { - code: `export default Component.extend({ - levelOfHappiness: observer("attitude", "health", () => { - }), - vehicle: alias("car") - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + levelOfHappiness: observer("attitude", "health", () => { + }), + vehicle: alias("car") + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" observer on line 2', - line: 4, + 'The "vehicle" single-line function should be above the "levelOfHappiness" observer on line 4', + line: 6, }, ], }, { - code: `export default Component.extend({ - levelOfHappiness: observer("attitude", "health", () => { - }), - aaa: computed("attitude", "health", () => { - }) - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + levelOfHappiness: observer("attitude", "health", () => { + }), + aaa: computed("attitude", "health", () => { + }) + }); + `, errors: [ { message: - 'The "aaa" multi-line function should be above the "levelOfHappiness" observer on line 2', - line: 4, + 'The "aaa" multi-line function should be above the "levelOfHappiness" observer on line 4', + line: 6, }, ], }, { - code: `export default Component.extend({ - init() { - }, - levelOfHappiness: observer("attitude", "health", () => { - }) - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() { + }, + levelOfHappiness: observer("attitude", "health", () => { + }) + }); + `, errors: [ { message: - 'The "levelOfHappiness" observer should be above the "init" lifecycle hook on line 2', - line: 4, + 'The "levelOfHappiness" observer should be above the "init" lifecycle hook on line 4', + line: 6, }, ], }, { - code: `export default Component.extend({ - actions: {}, - init() { - } - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + actions: {}, + init() { + } + }); + `, errors: [ { - message: 'The "init" lifecycle hook should be above the actions hash on line 2', - line: 3, + message: 'The "init" lifecycle hook should be above the actions hash on line 4', + line: 5, }, ], }, { - code: `export default Component.extend({ - customFunc() { - const foo = 'bar'; - }, - actions: {} - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + customFunc() { + const foo = 'bar'; + }, + actions: {} + }); + `, errors: [ { - message: 'The actions hash should be above the "customFunc" method on line 2', - line: 5, + message: 'The actions hash should be above the "customFunc" method on line 4', + line: 7, }, ], }, { - code: `export default Component.extend({ - tAction: test(function() { - }), - actions: {} - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + tAction: test(function() { + }), + actions: {} + }); + `, errors: [ { - message: 'The actions hash should be above the "tAction" method on line 2', - line: 4, + message: 'The actions hash should be above the "tAction" method on line 4', + line: 6, }, ], }, { - code: `export default Component.extend(TestMixin, TestMixin2, { - foo: alias("car"), + code: ` + import Component from '@ember/component'; + export default Component.extend(TestMixin, TestMixin2, { + foo: alias("car"), - levelOfHappiness: computed("attitude", "health", () => { - }), + levelOfHappiness: computed("attitude", "health", () => { + }), - vehicle: alias("car"), + vehicle: alias("car"), - role: "sloth", + role: "sloth", - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 4', - line: 7, + 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 6', + line: 9, }, { - message: 'The "role" property should be above the "foo" single-line function on line 2', - line: 9, + message: 'The "role" property should be above the "foo" single-line function on line 4', + line: 11, }, ], }, { - code: `let foo = 'foo'; + code: ` + import Component from '@ember/component'; + let foo = 'foo'; - export default Component.extend(TestMixin, TestMixin2, { - actions: {}, - [foo]: 'foo', - });`, + export default Component.extend(TestMixin, TestMixin2, { + actions: {}, + [foo]: 'foo', + }); + `, errors: [ { - message: 'The property should be above the actions hash on line 4', - line: 5, + message: 'The property should be above the actions hash on line 6', + line: 7, }, ], }, { filename: 'example-app/components/some-component/component.js', - code: `export default CustomComponent.extend({ - actions: {}, - role: "sloth", - });`, + code: ` + import CustomComponent from '@ember/component'; + export default CustomComponent.extend({ + actions: {}, + role: "sloth", + }); + `, errors: [ { - message: 'The "role" property should be above the actions hash on line 2', - line: 3, + message: 'The "role" property should be above the actions hash on line 4', + line: 5, }, ], }, { filename: 'example-app/components/some-component.js', - code: `export default CustomComponent.extend({ - actions: {}, - role: "sloth", - });`, + code: ` + import CustomComponent from '@ember/component'; + export default CustomComponent.extend({ + actions: {}, + role: "sloth", + }); + `, errors: [ { - message: 'The "role" property should be above the actions hash on line 2', - line: 3, + message: 'The "role" property should be above the actions hash on line 4', + line: 5, }, ], }, { filename: 'example-app/twisted-path/some-component.js', - code: `export default Component.extend({ - actions: {}, - role: "sloth", - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + actions: {}, + role: "sloth", + }); + `, errors: [ { - message: 'The "role" property should be above the actions hash on line 2', - line: 3, + message: 'The "role" property should be above the actions hash on line 4', + line: 5, }, ], }, { - code: `export default Component.extend({ - name: "Jon Snow", - actions: {}, - template: hbs\`Hello world {{name}}\`, - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + name: "Jon Snow", + actions: {}, + template: hbs\`Hello world {{name}}\`, + }); + `, errors: [ { - message: 'The "template" property should be above the actions hash on line 3', - line: 4, + message: 'The "template" property should be above the actions hash on line 5', + line: 6, }, ], }, { - code: `export default Component.extend({ - layout, - someComputedValue: computed.reads('count'), + code: ` + import Component from '@ember/component'; + export default Component.extend({ + layout, + someComputedValue: computed.reads('count'), - tabindex: -1, - });`, + tabindex: -1, + }); + `, errors: [ { message: - 'The "tabindex" property should be above the "someComputedValue" single-line function on line 3', - line: 5, + 'The "tabindex" property should be above the "someComputedValue" single-line function on line 5', + line: 7, }, ], }, { - code: `export default Component.extend({ - foo: computed(function() { - }).volatile(), - name: "Jon Snow", - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + foo: computed(function() { + }).volatile(), + name: "Jon Snow", + }); + `, errors: [ { - message: 'The "name" property should be above the "foo" multi-line function on line 2', - line: 4, + message: 'The "name" property should be above the "foo" multi-line function on line 4', + line: 6, }, ], }, { - code: `export default Component.extend({ - actions: {}, - didReceiveAttrs() {}, - willDestroyElement() {}, - didInsertElement() {}, - init() {}, - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + actions: {}, + didReceiveAttrs() {}, + willDestroyElement() {}, + didInsertElement() {}, + init() {}, + }); + `, errors: [ { message: - 'The "didReceiveAttrs" lifecycle hook should be above the actions hash on line 2', - line: 3, + 'The "didReceiveAttrs" lifecycle hook should be above the actions hash on line 4', + line: 5, }, { message: - 'The "willDestroyElement" lifecycle hook should be above the actions hash on line 2', - line: 4, + 'The "willDestroyElement" lifecycle hook should be above the actions hash on line 4', + line: 6, }, { message: - 'The "didInsertElement" lifecycle hook should be above the actions hash on line 2', - line: 5, + 'The "didInsertElement" lifecycle hook should be above the actions hash on line 4', + line: 7, }, { - message: 'The "init" lifecycle hook should be above the actions hash on line 2', - line: 6, + message: 'The "init" lifecycle hook should be above the actions hash on line 4', + line: 8, }, ], }, { - code: `export default Component.extend({ - foo: computed(function() { - }).volatile(), - onFoo() {}, - bar() { const foo = 'bar'}, - onBar: () => {} - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + foo: computed(function() { + }).volatile(), + onFoo() {}, + bar() { const foo = 'bar'}, + onBar: () => {} + }); + `, errors: [ { message: - 'The "onFoo" empty method should be above the "foo" multi-line function on line 2', - line: 4, + 'The "onFoo" empty method should be above the "foo" multi-line function on line 4', + line: 6, }, { message: - 'The "onBar" empty method should be above the "foo" multi-line function on line 2', - line: 6, + 'The "onBar" empty method should be above the "foo" multi-line function on line 4', + line: 8, }, ], }, { - code: `export default Component.extend({ - levelOfHappiness: computed("attitude", "health", () => { - }), + code: ` + import Component from '@ember/component'; + export default Component.extend({ + levelOfHappiness: computed("attitude", "health", () => { + }), - vehicle: alias("car"), + vehicle: alias("car"), - actions: {} - });`, - output: `export default Component.extend({ - vehicle: alias("car"), + actions: {} + }); + `, + output: ` + import Component from '@ember/component'; + export default Component.extend({ + vehicle: alias("car"), - levelOfHappiness: computed("attitude", "health", () => { - }), + levelOfHappiness: computed("attitude", "health", () => { + }), - actions: {} - });`, + actions: {} + }); + `, errors: [ { message: - 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 2', - line: 5, + 'The "vehicle" single-line function should be above the "levelOfHappiness" multi-line function on line 4', + line: 7, }, ], }, diff --git a/tests/lib/rules/order-in-controllers.js b/tests/lib/rules/order-in-controllers.js index 1deac90600..73fe96ad42 100644 --- a/tests/lib/rules/order-in-controllers.js +++ b/tests/lib/rules/order-in-controllers.js @@ -17,8 +17,13 @@ const eslintTester = new RuleTester({ eslintTester.run('order-in-controllers', rule, { valid: [ - 'export default Controller.extend();', - `export default Controller.extend({ + ` + import Controller from '@ember/controller'; + export default Controller.extend(); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend({ application: controller(), currentUser: service(), queryParams: [], @@ -27,8 +32,11 @@ eslintTester.run('order-in-controllers', rule, { _customAction() { const foo = 'bar'; }, _customAction2: function() { const foo = 'bar'; }, tSomeTask: task(function* () {}) - });`, - `export default Controller.extend({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend({ currentUser: inject(), queryParams: [], customProp: "test", @@ -36,31 +44,41 @@ eslintTester.run('order-in-controllers', rule, { _customAction() {}, _customAction2: function() {}, tSomeTask: task(function* () {}) - });`, - `export default Controller.extend({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend({ queryParams: [], customProp: "test", comp: computed("test", function() {}), obs: observer("asd", function() {}), actions: {} - });`, - `export default Controller.extend({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend({ customProp: "test", comp: computed("test", function() {}), comp2: computed("test", function() { }), actions: {}, _customAction() { const foo = 'bar'; } - });`, + }); + `, { - code: `export default Controller.extend({ - actions: {}, - comp: computed("test", function() {}), - customProp: "test", - comp2: computed("test", function() { - }), - _customAction() { const foo = 'bar'; } - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + actions: {}, + comp: computed("test", function() {}), + customProp: "test", + comp2: computed("test", function() { + }), + _customAction() { const foo = 'bar'; } + }); + `, options: [ { order: ['actions', 'single-line-function'], @@ -68,10 +86,13 @@ eslintTester.run('order-in-controllers', rule, { ], }, { - code: `export default Controller.extend({ - queryParams: [], - currentUser: service(), - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + currentUser: service(), + }); + `, options: [ { order: ['query-params', 'service'], @@ -79,10 +100,13 @@ eslintTester.run('order-in-controllers', rule, { ], }, { - code: `export default Controller.extend({ - queryParams: [], - application: controller(), - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + application: controller(), + }); + `, options: [ { order: ['query-params', 'controller'], @@ -90,185 +114,222 @@ eslintTester.run('order-in-controllers', rule, { ], }, ` - export default Controller.extend({ - foo: service(), - someProp: null, - init() { - this._super(...arguments); - }, - actions: { - onKeyPress: function (event) {} - } - }); - `, + import Controller from '@ember/controller'; + export default Controller.extend({ + foo: service(), + someProp: null, + init() { + this._super(...arguments); + }, + actions: { + onKeyPress: function (event) {} + } + }); + `, ` - export default Controller.extend({ - foo: service(), - init() { - this._super(...arguments); - }, - customFoo() {} - }); - `, + import Controller from '@ember/controller'; + export default Controller.extend({ + foo: service(), + init() { + this._super(...arguments); + }, + customFoo() {} + }); + `, ` - export default Controller.extend({ - foo: service(), - init() { - this._super(...arguments); - } - }); - `, + import Controller from '@ember/controller'; + export default Controller.extend({ + foo: service(), + init() { + this._super(...arguments); + } + }); + `, ], invalid: [ { - code: `export default Controller.extend({ - queryParams: [], - currentUser: service() - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + currentUser: service() + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 4', + line: 5, }, ], }, { - code: `export default Controller.extend({ - queryParams: [], - currentUser: inject() - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + currentUser: inject() + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 4', + line: 5, }, ], }, { - code: `export default Controller.extend({ - currentUser: service(), - customProp: "test", - queryParams: [] - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + currentUser: service(), + customProp: "test", + queryParams: [] + }); + `, errors: [ { - message: 'The "queryParams" property should be above the "customProp" property on line 3', - line: 4, + message: 'The "queryParams" property should be above the "customProp" property on line 5', + line: 6, }, ], }, { - code: `export default Controller.extend({ - queryParams: [], - actions: {}, - customProp: "test" - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + actions: {}, + customProp: "test" + }); + `, errors: [ { - message: 'The "customProp" property should be above the actions hash on line 3', - line: 4, + message: 'The "customProp" property should be above the actions hash on line 5', + line: 6, }, ], }, { - code: `export default Controller.extend({ - queryParams: [], - _customAction() { const foo = 'bar'; }, - actions: {} - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + _customAction() { const foo = 'bar'; }, + actions: {} + }); + `, errors: [ { - message: 'The actions hash should be above the "_customAction" method on line 3', - line: 4, + message: 'The actions hash should be above the "_customAction" method on line 5', + line: 6, }, ], }, { - code: `export default Controller.extend({ - test: "asd", - queryParams: [], - actions: {} - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + test: "asd", + queryParams: [], + actions: {} + }); + `, errors: [ { - message: 'The "queryParams" property should be above the "test" property on line 2', - line: 3, + message: 'The "queryParams" property should be above the "test" property on line 4', + line: 5, }, ], }, { - code: `export default Controller.extend({ - currentUser: service(), - application: controller() - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + currentUser: service(), + application: controller() + }); + `, errors: [ { message: - 'The "application" controller injection should be above the "currentUser" service injection on line 2', - line: 3, + 'The "application" controller injection should be above the "currentUser" service injection on line 4', + line: 5, }, ], }, { - code: `export default Controller.extend({ - test: "asd", - obs: observer("asd", function() {}), - comp: computed("asd", function() {}), - actions: {} - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + test: "asd", + obs: observer("asd", function() {}), + comp: computed("asd", function() {}), + actions: {} + }); + `, errors: [ { - message: 'The "comp" single-line function should be above the "obs" observer on line 3', - line: 4, + message: 'The "comp" single-line function should be above the "obs" observer on line 5', + line: 6, }, ], }, { filename: 'example-app/controllers/some-controller.js', - code: `export default CustomController.extend({ - queryParams: [], - currentUser: service() - });`, + code: ` + import CustomController from '@ember/controller'; + export default CustomController.extend({ + queryParams: [], + currentUser: service() + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 4', + line: 5, }, ], }, { filename: 'example-app/some-feature/controller.js', - code: `export default CustomController.extend({ - queryParams: [], - currentUser: service() - });`, + code: ` + import CustomController from '@ember/controller'; + export default CustomController.extend({ + queryParams: [], + currentUser: service() + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 4', + line: 5, }, ], }, { filename: 'example-app/twised-path/some-controller.js', - code: `export default Controller.extend({ - queryParams: [], - currentUser: service() - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + currentUser: service() + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 4', + line: 5, }, ], }, { code: ` + import Controller from '@ember/controller'; export default Controller.extend({ foo: service(), actions: { @@ -281,13 +342,14 @@ eslintTester.run('order-in-controllers', rule, { `, errors: [ { - message: 'The "init" lifecycle hook should be above the actions hash on line 4', - line: 7, + message: 'The "init" lifecycle hook should be above the actions hash on line 5', + line: 8, }, ], }, { code: ` + import Controller from '@ember/controller'; export default Controller.extend({ foo: service(), customFoo() {}, @@ -299,13 +361,14 @@ eslintTester.run('order-in-controllers', rule, { errors: [ { message: - 'The "init" lifecycle hook should be above the "customFoo" empty method on line 4', - line: 5, + 'The "init" lifecycle hook should be above the "customFoo" empty method on line 5', + line: 6, }, ], }, { code: ` + import Controller from '@ember/controller'; export default Controller.extend({ init() { this._super(...arguments); @@ -316,13 +379,14 @@ eslintTester.run('order-in-controllers', rule, { errors: [ { message: - 'The "foo" service injection should be above the "init" lifecycle hook on line 3', - line: 6, + 'The "foo" service injection should be above the "init" lifecycle hook on line 4', + line: 7, }, ], }, { code: ` + import Controller from '@ember/controller'; export default Controller.extend({ init() { this._super(...arguments); @@ -332,45 +396,53 @@ eslintTester.run('order-in-controllers', rule, { `, errors: [ { - message: 'The "someProp" property should be above the "init" lifecycle hook on line 3', - line: 6, + message: 'The "someProp" property should be above the "init" lifecycle hook on line 4', + line: 7, }, ], }, { code: // whitespace is preserved inside `` and it's breaking the test - `export default Controller.extend({ + `import Controller from '@ember/controller'; +export default Controller.extend({ queryParams: [], currentUser: service(), });`, - output: `export default Controller.extend({ + output: `import Controller from '@ember/controller'; +export default Controller.extend({ currentUser: service(), queryParams: [], });`, errors: [ { message: - 'The "currentUser" service injection should be above the "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the "queryParams" property on line 3', + line: 4, }, ], }, { - code: `export default Controller.extend({ - test: "asd", - queryParams: [], - actions: {} - });`, - output: `export default Controller.extend({ - queryParams: [], - test: "asd", - actions: {} - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + test: "asd", + queryParams: [], + actions: {} + }); + `, + output: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + queryParams: [], + test: "asd", + actions: {} + }); + `, errors: [ { - message: 'The "queryParams" property should be above the "test" property on line 2', - line: 3, + message: 'The "queryParams" property should be above the "test" property on line 4', + line: 5, }, ], }, diff --git a/tests/lib/rules/order-in-routes.js b/tests/lib/rules/order-in-routes.js index bcd7cede94..387a250e0a 100644 --- a/tests/lib/rules/order-in-routes.js +++ b/tests/lib/rules/order-in-routes.js @@ -17,8 +17,13 @@ const eslintTester = new RuleTester({ eslintTester.run('order-in-routes', rule, { valid: [ - 'export default Route.extend();', - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend(); + `, + ` + import Route from '@ember/routing/route'; + export default Route.extend({ currentUser: service(), queryParams: {}, customProp: "test", @@ -40,7 +45,9 @@ eslintTester.run('order-in-routes', rule, { _customAction2: function() { const foo = 'bar'; }, tSomeTask: task(function* () {}) });`, - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend({ currentUser: inject(), queryParams: {}, customProp: "test", @@ -55,7 +62,9 @@ eslintTester.run('order-in-routes', rule, { _customAction2: function() {}, tSomeTask: task(function* () {}) });`, - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend({ levelOfHappiness: computed("attitude", "health", () => { }), model() {}, @@ -64,12 +73,16 @@ eslintTester.run('order-in-routes', rule, { }, _customAction() { const foo = 'bar'; } });`, - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend({ init() {}, model() {}, render() {}, });`, - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend({ mergedProperties: {}, vehicle: alias("car"), levelOfHappiness: computed("attitude", "health", () => { @@ -77,18 +90,23 @@ eslintTester.run('order-in-routes', rule, { model() {}, actions: {} });`, - `export default Route.extend({ + ` + import Route from '@ember/routing/route'; + export default Route.extend({ mergedProperties: {}, test: "asd", vehicle: alias("car"), model() {} });`, { - code: `export default Route.extend({ - model() {}, - beforeModel() {}, - currentUser: service(), - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + model() {}, + beforeModel() {}, + currentUser: service(), + }); + `, options: [ { order: ['model', 'lifecycle-hook', 'service'], @@ -96,12 +114,15 @@ eslintTester.run('order-in-routes', rule, { ], }, { - code: `export default Route.extend({ - deactivate() {}, - beforeModel() {}, - currentUser: service(), - model() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + deactivate() {}, + beforeModel() {}, + currentUser: service(), + model() {} + }); + `, options: [ { order: ['lifecycle-hook', 'service', 'model'], @@ -109,13 +130,16 @@ eslintTester.run('order-in-routes', rule, { ], }, { - code: `export default Route.extend({ - deactivate() {}, - setupController() {}, - beforeModel() {}, - currentUser: service(), - model() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + deactivate() {}, + setupController() {}, + beforeModel() {}, + currentUser: service(), + model() {} + }); + `, options: [ { order: [['deactivate', 'setupController', 'beforeModel'], 'service', 'model'], @@ -123,271 +147,310 @@ eslintTester.run('order-in-routes', rule, { ], }, ` - export default Route.extend({ - foo: service(), - init() { - this._super(...arguments); - }, - actions: {} - }); - `, + import Route from '@ember/routing/route'; + export default Route.extend({ + foo: service(), + init() { + this._super(...arguments); + }, + actions: {} + }); + `, ` - export default Route.extend({ - foo: service(), - init() { - this._super(...arguments); - }, - customFoo() {} - }); - `, + import Route from '@ember/routing/route'; + export default Route.extend({ + foo: service(), + init() { + this._super(...arguments); + }, + customFoo() {} + }); + `, ], invalid: [ { - code: `export default Route.extend({ - queryParams: {}, - currentUser: service(), - customProp: "test", - beforeModel() {}, - model() {}, - vehicle: alias("car"), - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + queryParams: {}, + currentUser: service(), + customProp: "test", + beforeModel() {}, + model() {}, + vehicle: alias("car"), + actions: {}, + _customAction() {} + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the inherited "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the inherited "queryParams" property on line 4', + line: 5, }, { message: - 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 5', - line: 7, + 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 7', + line: 9, }, ], }, { - code: `export default Route.extend({ - queryParams: {}, - currentUser: inject(), - customProp: "test", - beforeModel() {}, - model() {}, - vehicle: alias("car"), - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + queryParams: {}, + currentUser: inject(), + customProp: "test", + beforeModel() {}, + model() {}, + vehicle: alias("car"), + actions: {}, + _customAction() {} + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the inherited "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the inherited "queryParams" property on line 4', + line: 5, }, { message: - 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 5', - line: 7, + 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 7', + line: 9, }, ], }, { - code: `export default Route.extend({ - customProp: "test", - queryParams: {}, - beforeModel() {}, - model() {}, - actions: {}, - _customAction() {}, - levelOfHappiness: computed("attitude", "health", () => { - }) - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + customProp: "test", + queryParams: {}, + beforeModel() {}, + model() {}, + actions: {}, + _customAction() {}, + levelOfHappiness: computed("attitude", "health", () => { + }) + }); + `, errors: [ { message: - 'The inherited "queryParams" property should be above the "customProp" property on line 2', - line: 3, + 'The inherited "queryParams" property should be above the "customProp" property on line 4', + line: 5, }, { message: - 'The "levelOfHappiness" multi-line function should be above the "beforeModel" lifecycle hook on line 4', - line: 8, + 'The "levelOfHappiness" multi-line function should be above the "beforeModel" lifecycle hook on line 6', + line: 10, }, ], }, { - code: `export default Route.extend({ - customProp: "test", - queryParams: {}, - model() {}, - beforeModel() {}, - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + customProp: "test", + queryParams: {}, + model() {}, + beforeModel() {}, + actions: {}, + _customAction() {} + }); + `, errors: [ { message: - 'The inherited "queryParams" property should be above the "customProp" property on line 2', - line: 3, + 'The inherited "queryParams" property should be above the "customProp" property on line 4', + line: 5, }, { - message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 4', - line: 5, + message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 6', + line: 7, }, ], }, { - code: `export default Route.extend({ - queryParams: {}, - vehicle: alias("car"), - customProp: "test", - model() {}, - _customAction() { const foo = 'bar'; }, - actions: {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + queryParams: {}, + vehicle: alias("car"), + customProp: "test", + model() {}, + _customAction() { const foo = 'bar'; }, + actions: {} + }); + `, errors: [ { message: - 'The "customProp" property should be above the "vehicle" single-line function on line 3', - line: 4, + 'The "customProp" property should be above the "vehicle" single-line function on line 5', + line: 6, }, { - message: 'The actions hash should be above the "_customAction" method on line 6', - line: 7, + message: 'The actions hash should be above the "_customAction" method on line 8', + line: 9, }, ], }, { - code: `export default Route.extend({ - model() {}, - customProp: "test", - actions: {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + model() {}, + customProp: "test", + actions: {} + }); + `, errors: [ { - message: 'The "customProp" property should be above the "model" hook on line 2', - line: 3, + message: 'The "customProp" property should be above the "model" hook on line 4', + line: 5, }, ], }, { - code: `export default Route.extend({ - test: "asd", - mergedProperties: {}, - model() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + test: "asd", + mergedProperties: {}, + model() {} + }); + `, errors: [ { message: - 'The inherited "mergedProperties" property should be above the "test" property on line 2', - line: 3, + 'The inherited "mergedProperties" property should be above the "test" property on line 4', + line: 5, }, ], }, { - code: `export default Route.extend({ - currentUser: service(), - queryParams: {}, - customProp: "test", - vehicle: alias("car"), - levelOfHappiness: computed("attitude", "health", () => { - }), - beforeModel() {}, - model() {}, - afterModel() {}, - setupController() {}, - redirect() {}, - serialize() {}, - activate() {}, - deactivate() {}, - renderTemplate() {}, - resetController() {}, - actions: {}, - _customAction() {}, - _customAction2: function() {}, - tSomeTask: task(function* () {}) - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + currentUser: service(), + queryParams: {}, + customProp: "test", + vehicle: alias("car"), + levelOfHappiness: computed("attitude", "health", () => { + }), + beforeModel() {}, + model() {}, + afterModel() {}, + setupController() {}, + redirect() {}, + serialize() {}, + activate() {}, + deactivate() {}, + renderTemplate() {}, + resetController() {}, + actions: {}, + _customAction() {}, + _customAction2: function() {}, + tSomeTask: task(function* () {}) + }); + `, errors: [ { message: - 'The "redirect" lifecycle hook should be above the "setupController" lifecycle hook on line 11', - line: 12, + 'The "redirect" lifecycle hook should be above the "setupController" lifecycle hook on line 13', + line: 14, }, { message: - 'The "serialize" lifecycle hook should be above the "setupController" lifecycle hook on line 11', - line: 13, + 'The "serialize" lifecycle hook should be above the "setupController" lifecycle hook on line 13', + line: 15, }, { message: - 'The "activate" lifecycle hook should be above the "setupController" lifecycle hook on line 11', - line: 14, + 'The "activate" lifecycle hook should be above the "setupController" lifecycle hook on line 13', + line: 16, }, { message: - 'The "renderTemplate" lifecycle hook should be above the "deactivate" lifecycle hook on line 15', - line: 16, + 'The "renderTemplate" lifecycle hook should be above the "deactivate" lifecycle hook on line 17', + line: 18, }, { message: - 'The "resetController" lifecycle hook should be above the "deactivate" lifecycle hook on line 15', - line: 17, + 'The "resetController" lifecycle hook should be above the "deactivate" lifecycle hook on line 17', + line: 19, }, ], }, { - code: `export default Route.extend({ - test: "asd", - _test2() { const foo = 'bar'; }, - model() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + test: "asd", + _test2() { const foo = 'bar'; }, + model() {} + }); + `, errors: [ { - message: 'The "model" hook should be above the "_test2" method on line 3', - line: 4, + message: 'The "model" hook should be above the "_test2" method on line 5', + line: 6, }, ], }, { filename: 'example-app/routes/some-route.js', - code: `export default CustomRoute.extend({ - model() {}, - test: "asd", - });`, + code: ` + import CustomRoute from '@ember/routing/route'; + export default CustomRoute.extend({ + model() {}, + test: "asd", + }); + `, errors: [ { - message: 'The "test" property should be above the "model" hook on line 2', - line: 3, + message: 'The "test" property should be above the "model" hook on line 4', + line: 5, }, ], }, { filename: 'example-app/some-feature/route.js', - code: `export default CustomRoute.extend({ - model() {}, - test: "asd", - });`, + code: ` + import CustomRoute from '@ember/routing/route'; + export default CustomRoute.extend({ + model() {}, + test: "asd", + }); + `, errors: [ { - message: 'The "test" property should be above the "model" hook on line 2', - line: 3, + message: 'The "test" property should be above the "model" hook on line 4', + line: 5, }, ], }, { filename: 'example-app/twisted-path/some-file.js', - code: `export default Route.extend({ - model() {}, - test: "asd", - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + model() {}, + test: "asd", + }); + `, errors: [ { - message: 'The "test" property should be above the "model" hook on line 2', - line: 3, + message: 'The "test" property should be above the "model" hook on line 4', + line: 5, }, ], }, { code: ` + import Route from '@ember/routing/route'; export default Route.extend({ foo: service(), actions: {}, @@ -398,13 +461,14 @@ eslintTester.run('order-in-routes', rule, { `, errors: [ { - message: 'The "init" lifecycle hook should be above the actions hash on line 4', - line: 5, + message: 'The "init" lifecycle hook should be above the actions hash on line 5', + line: 6, }, ], }, { code: ` + import Route from '@ember/routing/route'; export default Route.extend({ foo: service(), customFoo() {}, @@ -416,13 +480,14 @@ eslintTester.run('order-in-routes', rule, { errors: [ { message: - 'The "init" lifecycle hook should be above the "customFoo" empty method on line 4', - line: 5, + 'The "init" lifecycle hook should be above the "customFoo" empty method on line 5', + line: 6, }, ], }, { code: ` + import Route from '@ember/routing/route'; export default Route.extend({ init() { this._super(...arguments); @@ -433,13 +498,14 @@ eslintTester.run('order-in-routes', rule, { errors: [ { message: - 'The "foo" service injection should be above the "init" lifecycle hook on line 3', - line: 6, + 'The "foo" service injection should be above the "init" lifecycle hook on line 4', + line: 7, }, ], }, { code: ` + import Route from '@ember/routing/route'; export default Route.extend({ init() { this._super(...arguments); @@ -449,114 +515,133 @@ eslintTester.run('order-in-routes', rule, { `, errors: [ { - message: 'The "someProp" property should be above the "init" lifecycle hook on line 3', - line: 6, + message: 'The "someProp" property should be above the "init" lifecycle hook on line 4', + line: 7, }, ], }, { - code: `export default Route.extend({ - queryParams: {}, - currentUser: service(), - customProp: "test", - beforeModel() {}, - model() {}, - vehicle: alias("car"), - actions: {}, - _customAction() {} - });`, - output: `export default Route.extend({ - currentUser: service(), - queryParams: {}, - customProp: "test", - vehicle: alias("car"), - beforeModel() {}, - model() {}, - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + queryParams: {}, + currentUser: service(), + customProp: "test", + beforeModel() {}, + model() {}, + vehicle: alias("car"), + actions: {}, + _customAction() {} + }); + `, + output: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + currentUser: service(), + queryParams: {}, + customProp: "test", + vehicle: alias("car"), + beforeModel() {}, + model() {}, + actions: {}, + _customAction() {} + }); + `, errors: [ { message: - 'The "currentUser" service injection should be above the inherited "queryParams" property on line 2', - line: 3, + 'The "currentUser" service injection should be above the inherited "queryParams" property on line 4', + line: 5, }, { message: - 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 5', - line: 7, + 'The "vehicle" single-line function should be above the "beforeModel" lifecycle hook on line 7', + line: 9, }, ], }, { - code: `export default Route.extend({ - customProp: "test", - // queryParams line comment - queryParams: {}, - model() {}, - /** - * actions block comment - */ - actions: {}, - _customAction() {} - });`, - output: `export default Route.extend({ - // queryParams line comment - queryParams: {}, - customProp: "test", - model() {}, - /** - * actions block comment - */ - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + customProp: "test", + // queryParams line comment + queryParams: {}, + model() {}, + /** + * actions block comment + */ + actions: {}, + _customAction() {} + }); + `, + output: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + // queryParams line comment + queryParams: {}, + customProp: "test", + model() {}, + /** + * actions block comment + */ + actions: {}, + _customAction() {} + }); + `, errors: [ { message: - 'The inherited "queryParams" property should be above the "customProp" property on line 2', - line: 4, + 'The inherited "queryParams" property should be above the "customProp" property on line 4', + line: 6, }, ], }, { - code: `export default Route.extend({ - customProp: "test", - model() {}, - /** - * beforeModel block comment - */ - beforeModel() {}, - /** - * actions block comment - */ - actions: {}, - _customAction() {} - });`, - output: `export default Route.extend({ - customProp: "test", - /** - * beforeModel block comment - */ - beforeModel() {}, - model() {}, - /** - * actions block comment - */ - actions: {}, - _customAction() {} - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + customProp: "test", + model() {}, + /** + * beforeModel block comment + */ + beforeModel() {}, + /** + * actions block comment + */ + actions: {}, + _customAction() {} + }); + `, + output: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + customProp: "test", + /** + * beforeModel block comment + */ + beforeModel() {}, + model() {}, + /** + * actions block comment + */ + actions: {}, + _customAction() {} + }); + `, errors: [ { - message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 3', - line: 7, + message: 'The "beforeModel" lifecycle hook should be above the "model" hook on line 5', + line: 9, }, ], }, { code: // whitespace is preserved inside `` and it's breaking the test - `export default Route.extend({ + `import Route from '@ember/routing/route'; +export default Route.extend({ customProp: "test", /** * actions block comment @@ -567,7 +652,8 @@ eslintTester.run('order-in-routes', rule, { */ beforeModel() {} });`, - output: `export default Route.extend({ + output: `import Route from '@ember/routing/route'; +export default Route.extend({ customProp: "test", /** * beforeModel block comment @@ -580,52 +666,60 @@ eslintTester.run('order-in-routes', rule, { });`, errors: [ { - message: 'The "beforeModel" lifecycle hook should be above the actions hash on line 6', - line: 10, + message: 'The "beforeModel" lifecycle hook should be above the actions hash on line 7', + line: 11, }, ], }, { code: // whitespace is preserved inside `` and it's breaking the test - `export default Route.extend({ + `import Route from '@ember/routing/route'; +export default Route.extend({ model() {}, test: "asd" });`, - output: `export default Route.extend({ + output: `import Route from '@ember/routing/route'; +export default Route.extend({ test: "asd", model() {}, });`, errors: [ { - message: 'The "test" property should be above the "model" hook on line 2', - line: 3, + message: 'The "test" property should be above the "model" hook on line 3', + line: 4, }, ], }, { - code: `export default Route.extend({ + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ - model() {}, + model() {}, - test: "asd", + test: "asd", - actions: {} + actions: {} - });`, - output: `export default Route.extend({ + }); + `, + output: ` + import Route from '@ember/routing/route'; + export default Route.extend({ - test: "asd", + test: "asd", - model() {}, + model() {}, - actions: {} + actions: {} - });`, + }); + `, errors: [ { - message: 'The "test" property should be above the "model" hook on line 3', - line: 5, + message: 'The "test" property should be above the "model" hook on line 5', + line: 7, }, ], }, diff --git a/tests/lib/rules/require-super-in-init.js b/tests/lib/rules/require-super-in-init.js index ff61ce00c4..e4b7e1c7d7 100644 --- a/tests/lib/rules/require-super-in-init.js +++ b/tests/lib/rules/require-super-in-init.js @@ -17,425 +17,448 @@ const message = 'Call this._super(...arguments) in init hook'; eslintTester.run('require-super-in-init', rule, { valid: [ - `export default Component.extend({ + ` + import Component from '@ember/component'; + export default Component.extend({ init() { return this._super(...arguments); } - });`, - `export default Route.extend({ + }); + `, + ` + import Route from '@ember/routing/route'; + export default Route.extend({ init() { return this._super(...arguments); } - });`, - `export default Controller.extend({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend({ init() { return this._super(...arguments); } - });`, - `export default Mixin.extend({ + }); + `, + ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ init() { return this._super(...arguments); } - });`, - `export default Service.extend({ + }); + `, + ` + import Service from '@ember/service'; + export default Service.extend({ init() { return this._super(...arguments); } - });`, - `export default Component({ - init() { - return this._super(...arguments); - } - });`, - `export default Route({ - init() { - return this._super(...arguments); - } - });`, - `export default Controller({ - init() { - return this._super(...arguments); - } - });`, - `export default Mixin({ - init() { - return this._super(...arguments); - } - });`, - `export default Service({ - init() { - return this._super(...arguments); - } - });`, - 'export default Component.extend();', - 'export default Route.extend();', - 'export default Controller.extend();', - 'export default Mixin.extend();', - 'export default Service.extend();', - `export default Component({ + }); + `, + ` + import Component from '@ember/component'; + export default Component.extend(); + `, + ` + import Route from '@ember/routing/route'; + export default Route.extend(); + `, + ` + import Controller from '@ember/controller'; + export default Controller.extend(); + `, + ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend(); + `, + ` + import Service from '@ember/service'; + export default Service.extend(); + `, + ` + import Component from '@ember/component'; + export default Component({ init() { this._super(...arguments); }, - });`, - `export default Route({ + }); + `, + ` + import Route from '@ember/routing/route'; + export default Route({ init() { this._super(...arguments); }, - });`, - `export default Controller({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller({ init() { this._super(...arguments); }, - });`, - `export default Mixin({ + }); + `, + ` + import Mixin from '@ember/object/mixin'; + export default Mixin({ init() { this._super(...arguments); }, - });`, - `export default Service({ + }); + `, + ` + import Service from '@ember/service'; + export default Service({ init() { this._super(...arguments); }, - });`, - `export default Component({ + }); + `, + ` + import Component from '@ember/component'; + export default Component({ init: function() { this._super(...arguments); }, - });`, - `export default Route({ + }); + `, + ` + import Route from '@ember/routing/route'; + export default Route({ init: function() { this._super(...arguments); }, - });`, - `export default Controller({ + }); + `, + ` + import Controller from '@ember/controller'; + export default Controller({ init: function() { this._super(...arguments); }, - });`, - `export default Mixin({ + }); + `, + ` + import Mixin from '@ember/object/mixin'; + export default Mixin({ init: function() { this._super(...arguments); }, - });`, - `export default Service({ + }); + `, + ` + import Service from '@ember/service'; + export default Service({ init: function() { this._super(...arguments); }, - });`, - `export default Service({ + }); + `, + ` + import Service from '@ember/service'; + export default Service({ init - });`, + }); + `, ], invalid: [ { - code: `export default Component.extend({ - init() {}, - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() {}, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component.extend({ - init() { - this.set('prop', 'value'); - }, - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() { + this.set('prop', 'value'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component.extend({ - init() { - this.set('prop', 'value'); - this.set('prop2', 'value2'); - }, - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() { + this.set('prop', 'value'); + this.set('prop2', 'value2'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Route.extend({ - init() {}, - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + init() {}, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Route.extend({ - init() { - this.set('prop', 'value'); - }, - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + init() { + this.set('prop', 'value'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Route.extend({ - init() { - this.set('prop', 'value'); - this.set('prop2', 'value2'); - }, - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + init() { + this.set('prop', 'value'); + this.set('prop2', 'value2'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Controller.extend({ - init() {}, - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + init() {}, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Controller.extend({ - init() { - this.set('prop', 'value'); - }, - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + init() { + this.set('prop', 'value'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Controller.extend({ - init() { - this.set('prop', 'value'); - this.set('prop2', 'value2'); - }, - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + init() { + this.set('prop', 'value'); + this.set('prop2', 'value2'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Mixin.extend({ - init() {}, - });`, + code: ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ + init() {}, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Mixin.extend({ - init() { - this.set('prop', 'value'); - }, - });`, + code: ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ + init() { + this.set('prop', 'value'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Mixin.extend({ - init() { - this.set('prop', 'value'); - this.set('prop2', 'value2'); - }, - });`, + code: ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ + init() { + this.set('prop', 'value'); + this.set('prop2', 'value2'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Service.extend({ - init() {}, - });`, + code: ` + import Service from '@ember/service'; + export default Service.extend({ + init() {}, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Service.extend({ - init() { - this.set('prop', 'value'); - }, - });`, + code: ` + import Service from '@ember/service'; + export default Service.extend({ + init() { + this.set('prop', 'value'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Service.extend({ - init() { - this.set('prop', 'value'); - this.set('prop2', 'value2'); - }, - });`, + code: ` + import Service from '@ember/service'; + export default Service.extend({ + init() { + this.set('prop', 'value'); + this.set('prop2', 'value2'); + }, + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component.extend({ - init() { - return; - } - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() { + return; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Route.extend({ - init() { - return; - } - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + init() { + return; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Controller.extend({ - init() { - return; - } - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + init() { + return; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Mixin.extend({ - init() { - return; - } - });`, + code: ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ + init() { + return; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Service.extend({ - init() { - return; - } - });`, + code: ` + import Service from '@ember/service'; + export default Service.extend({ + init() { + return; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Component({ - init() { - return; - } - });`, + code: ` + import Component from '@ember/component'; + export default Component.extend({ + init() { + return 'meh'; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Route({ - init() { - return; - } - });`, + code: ` + import Route from '@ember/routing/route'; + export default Route.extend({ + init() { + return 'meh'; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Controller({ - init() { - return; - } - });`, + code: ` + import Controller from '@ember/controller'; + export default Controller.extend({ + init() { + return 'meh'; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Mixin({ - init() { - return; - } - });`, + code: ` + import Mixin from '@ember/object/mixin'; + export default Mixin.extend({ + init() { + return 'meh'; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, { - code: `export default Service({ - init() { - return; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Component.extend({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Route.extend({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Controller.extend({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Mixin.extend({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Service.extend({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Component({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Route({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Controller({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Mixin({ - init() { - return 'meh'; - } - });`, - output: null, - errors: [{ message, line: 2 }], - }, - { - code: `export default Service({ - init() { - return 'meh'; - } - });`, + code: ` + import Service from '@ember/service'; + export default Service.extend({ + init() { + return 'meh'; + } + }); + `, output: null, - errors: [{ message, line: 2 }], + errors: [{ message, line: 4 }], }, ], }); diff --git a/tests/lib/utils/ember-test.js b/tests/lib/utils/ember-test.js index ffe92b96f8..9f0681e4a6 100644 --- a/tests/lib/utils/ember-test.js +++ b/tests/lib/utils/ember-test.js @@ -100,55 +100,73 @@ describe('isTestFile', () => { describe('isEmberCoreModule', () => { it('should check if current file is a component', () => { const context = new FauxContext( - 'CustomComponent.extend()', + ` + import CustomComponent from '@ember/component'; + CustomComponent.extend() + `, 'example-app/components/path/to/some-component.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Component')).toBeTruthy(); }); it('should check if current file is a component', () => { const context = new FauxContext( - 'Component.extend()', + ` + import CustomComponent from '@ember/component'; + CustomComponent.extend() + `, 'example-app/some-twisted-path/some-component.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Component')).toBeTruthy(); }); it('should check if current file is a controller', () => { const context = new FauxContext( - 'CustomController.extend()', + ` + import CustomController from '@ember/controller'; + CustomController.extend() + `, 'example-app/controllers/path/to/some-controller.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Controller')).toBeTruthy(); }); it('should check if current file is a controller', () => { const context = new FauxContext( - 'Controller.extend()', + ` + import CustomController from '@ember/controller'; + CustomController.extend() + `, 'example-app/some-twisted-path/some-controller.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Controller')).toBeTruthy(); }); it('should check if current file is a route', () => { const context = new FauxContext( - 'CustomRoute.extend()', + ` + import CustomRoute from '@ember/routing/route'; + CustomRoute.extend() + `, 'example-app/routes/path/to/some-route.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy(); }); it('should check if current file is a route', () => { const context = new FauxContext( - 'Route.extend()', + ` + import Route from '@ember/routing/route'; + Route.extend() + `, 'example-app/some-twisted-path/some-route.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy(); }); @@ -170,8 +188,11 @@ describe('isEmberComponent', () => { }); it('it should detect Component when using local module Component', () => { - const context = new FauxContext('Component.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import Component from '@ember/component'; + Component.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberComponent(context, node)).toBeTruthy(); }); @@ -203,10 +224,13 @@ describe('isEmberComponent', () => { it('it should detect Component when file path is provided', () => { const context = new FauxContext( - 'CustomComponent.extend()', + ` + import CustomComponent from '@ember/component'; + CustomComponent.extend() + `, 'example-app/components/path/to/some-component.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberComponent(context, node)).toBeTruthy(); }); @@ -230,8 +254,11 @@ describe('isEmberController', () => { }); it('it should detect Controller when using local module Controller', () => { - const context = new FauxContext('Controller.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import Controller from '@ember/controller'; + Controller.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberController(context, node)).toBeTruthy(); }); @@ -263,10 +290,13 @@ describe('isEmberController', () => { it('it should detect Controller when file path is provided', () => { const context = new FauxContext( - 'CustomController.extend()', + ` + import CustomController from '@ember/controller'; + CustomController.extend() + `, 'example-app/controllers/path/to/some-feature.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberController(context, node)).toBeTruthy(); }); @@ -290,8 +320,11 @@ describe('isEmberRoute', () => { }); it('should detect Route when using local module Route', () => { - const context = new FauxContext('Route.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import Route from '@ember/routing/route'; + Route.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberRoute(context, node)).toBeTruthy(); }); @@ -323,10 +356,13 @@ describe('isEmberRoute', () => { it('it should detect Route when file path is provided', () => { const context = new FauxContext( - 'CustomRoute.extend()', + ` + import CustomRoute from '@ember/routing/route'; + CustomRoute.extend() + `, 'example-app/routes/path/to/some-feature.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberRoute(context, node)).toBeTruthy(); }); @@ -384,8 +420,11 @@ describe('isEmberService', () => { }); it('should detect Service when using local module Service', () => { - const context = new FauxContext('Service.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import Service from '@ember/service'; + Service.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberService(context, node)).toBeTruthy(); }); @@ -417,10 +456,13 @@ describe('isEmberService', () => { it('it should detect Service when file path is provided', () => { const context = new FauxContext( - 'CustomService.extend()', + ` + import CustomService from '@ember/service'; + CustomService.extend() + `, 'example-app/services/path/to/some-feature.js' ); - const node = context.ast.body[0].expression; + const node = context.ast.body[1].expression; expect(emberUtils.isEmberService(context, node)).toBeTruthy(); }); @@ -449,8 +491,11 @@ describe('isEmberArrayProxy', () => { }); it('should detect when using local module', () => { - const context = new FauxContext('ArrayProxy.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import ArrayProxy from '@ember/array/proxy'; + ArrayProxy.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberArrayProxy(context, node)).toBeTruthy(); }); @@ -502,8 +547,11 @@ describe('isEmberObjectProxy', () => { }); it('should detect when using local module', () => { - const context = new FauxContext('ObjectProxy.extend()'); - const node = context.ast.body[0].expression; + const context = new FauxContext(` + import ObjectProxy from '@ember/object/proxy'; + ObjectProxy.extend() + `); + const node = context.ast.body[1].expression; expect(emberUtils.isEmberObjectProxy(context, node)).toBeTruthy(); }); From 535cb407e278ce05a8f0d9b5a2b8978591cca186 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Thu, 21 Nov 2019 00:54:27 -0500 Subject: [PATCH 5/6] refactor: avoid using file name in rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original logic for determining if a Identifier was of a specific Ember class used the file name to make this decision, rather than resolving the import of the Identifier back to the Ember module it was imported from. This ended up causing a number of issues in lint rule in cases where people places a class in a location that was unexpected based on Ember’s convention. Such issues include #601 and #430. Making this change avoids the need for something like #213, which adds additional logic based on file names to prevent some of these issues. Closes #590 --- lib/rules/alias-model-in-controller.js | 4 +- lib/rules/avoid-using-needs-in-controllers.js | 31 +++++++----- lib/rules/no-actions-hash.js | 4 +- lib/rules/no-get.js | 7 ++- lib/rules/no-new-mixins.js | 4 +- lib/rules/no-on-calls-in-components.js | 4 +- lib/rules/order-in-components.js | 4 +- lib/rules/order-in-controllers.js | 4 +- lib/rules/order-in-models.js | 4 +- lib/rules/order-in-routes.js | 4 +- lib/rules/require-super-in-init.js | 4 +- lib/utils/ember.js | 48 ++++++++++--------- tests/lib/utils/ember-test.js | 6 --- 13 files changed, 76 insertions(+), 52 deletions(-) diff --git a/lib/rules/alias-model-in-controller.js b/lib/rules/alias-model-in-controller.js index 4c0bea9b5b..afe4a0f31f 100644 --- a/lib/rules/alias-model-in-controller.js +++ b/lib/rules/alias-model-in-controller.js @@ -29,7 +29,9 @@ module.exports = { }; return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isEmberController(context, node)) { return; } diff --git a/lib/rules/avoid-using-needs-in-controllers.js b/lib/rules/avoid-using-needs-in-controllers.js index 1066fd9f4f..de4a9846d2 100644 --- a/lib/rules/avoid-using-needs-in-controllers.js +++ b/lib/rules/avoid-using-needs-in-controllers.js @@ -27,22 +27,27 @@ module.exports = { context.report(node, message); }; - return { - CallExpression(node) { - const isReopenNode = ember.isReopenObject(node) || ember.isReopenClassObject(node); + function checkMemberExpression(memberExpressionNode) { + const node = memberExpressionNode.parent; - if (!ember.isEmberController(context, node) && !isReopenNode) { - return; - } + if (!ember.isEmberController(context, node)) { + return; + } - const properties = ember.getModuleProperties(node); + const properties = ember.getModuleProperties(node); - properties.forEach(property => { - if (property.key.name === 'needs') { - report(property); - } - }); - }, + properties.forEach(property => { + if (property.key.name === 'needs') { + report(property); + } + }); + } + + return { + 'CallExpression > MemberExpression[property.name="extend"]': checkMemberExpression, + 'CallExpression > MemberExpression[property.name="reopen"]': checkMemberExpression, + 'CallExpression > MemberExpression[property.name="reopenClass"]': checkMemberExpression, + 'CallExpression > MemberExpression[property.value="extend"]': checkMemberExpression, }; }, }; diff --git a/lib/rules/no-actions-hash.js b/lib/rules/no-actions-hash.js index d1a0cf44ca..7dffbfb240 100644 --- a/lib/rules/no-actions-hash.js +++ b/lib/rules/no-actions-hash.js @@ -41,7 +41,9 @@ module.exports = { reportActionsProp(utils.findNodes(node.body.body, 'ClassProperty')); } }, - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (inClassWhichCanContainActions(context, node)) { reportActionsProp(ember.getModuleProperties(node)); } diff --git a/lib/rules/no-get.js b/lib/rules/no-get.js index 2d1eb447d2..9aa6e409c5 100644 --- a/lib/rules/no-get.js +++ b/lib/rules/no-get.js @@ -84,10 +84,13 @@ module.exports = { // Check for situations which the rule should ignore. // ************************** - if (emberUtils.isEmberProxy(context, node)) { + if (emberUtils.isEmberObject(node) && emberUtils.isEmberProxy(context, node)) { currentProxyObject = node; // Keep track of being inside a proxy object. } - if (emberUtils.isEmberObjectImplementingUnknownProperty(node)) { + if ( + emberUtils.isEmberObject(node) && + emberUtils.isEmberObjectImplementingUnknownProperty(node) + ) { currentClassWithUnknownPropertyMethod = node; } if (currentProxyObject || currentClassWithUnknownPropertyMethod) { diff --git a/lib/rules/no-new-mixins.js b/lib/rules/no-new-mixins.js index 97c38bb45f..9cc0a10d1a 100644 --- a/lib/rules/no-new-mixins.js +++ b/lib/rules/no-new-mixins.js @@ -26,7 +26,9 @@ module.exports = { create(context) { return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="create"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (ember.isEmberMixin(context, node)) { context.report(node, ERROR_MESSAGE); } diff --git a/lib/rules/no-on-calls-in-components.js b/lib/rules/no-on-calls-in-components.js index 2274ce6bbf..1d0459618d 100644 --- a/lib/rules/no-on-calls-in-components.js +++ b/lib/rules/no-on-calls-in-components.js @@ -63,7 +63,9 @@ module.exports = { }; return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isEmberComponent(context, node)) { return; } diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js index d0ad40088d..410f1a194b 100644 --- a/lib/rules/order-in-components.js +++ b/lib/rules/order-in-components.js @@ -84,7 +84,9 @@ module.exports = { } return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isEmberComponent(context, node)) { return; } diff --git a/lib/rules/order-in-controllers.js b/lib/rules/order-in-controllers.js index 532d22b98f..77aa2b8bec 100644 --- a/lib/rules/order-in-controllers.js +++ b/lib/rules/order-in-controllers.js @@ -56,7 +56,9 @@ module.exports = { : ORDER; return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isEmberController(context, node)) { return; } diff --git a/lib/rules/order-in-models.js b/lib/rules/order-in-models.js index 491c247d19..809ad57b55 100644 --- a/lib/rules/order-in-models.js +++ b/lib/rules/order-in-models.js @@ -46,7 +46,9 @@ module.exports = { const filePath = context.getFilename(); return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isDSModel(node, filePath)) { return; } diff --git a/lib/rules/order-in-routes.js b/lib/rules/order-in-routes.js index 6a9064a61e..a1536fb833 100644 --- a/lib/rules/order-in-routes.js +++ b/lib/rules/order-in-routes.js @@ -79,7 +79,9 @@ module.exports = { } return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if (!ember.isEmberRoute(context, node)) { return; } diff --git a/lib/rules/require-super-in-init.js b/lib/rules/require-super-in-init.js index 4592dd5b89..2edf4e80a2 100644 --- a/lib/rules/require-super-in-init.js +++ b/lib/rules/require-super-in-init.js @@ -88,7 +88,9 @@ module.exports = { }; return { - CallExpression(node) { + 'CallExpression > MemberExpression[property.name="extend"]'(memberExpressionNode) { + const node = memberExpressionNode.parent; + if ( !ember.isEmberComponent(context, node) && !ember.isEmberController(context, node) && diff --git a/lib/utils/ember.js b/lib/utils/ember.js index b85ac09819..ab86e0a71a 100644 --- a/lib/utils/ember.js +++ b/lib/utils/ember.js @@ -1,6 +1,6 @@ 'use strict'; -const { getSourceModuleNameForIdentifier } = require('./import-info'); +const { getSourceModuleNameForIdentifier, isDefaultImport } = require('./import-info'); const utils = require('./utils'); const types = require('./types'); const assert = require('assert'); @@ -71,17 +71,6 @@ const CORE_MODULE_IMPORT_PATHS = { ObjectProxy: '@ember/object/proxy', }; -function isClassicEmberCoreModule(node, module, filePath) { - const isExtended = isEmberObject(node); - let isModuleByPath; - - if (filePath) { - isModuleByPath = isModuleByFilePath(filePath, module.toLowerCase()) && isExtended; - } - - return isModule(node, module) || isModuleByPath; -} - function isLocalModule(callee, element) { return ( (types.isIdentifier(callee) && callee.name === element) || @@ -170,26 +159,41 @@ function isReopenObject(node) { } function isEmberCoreModule(context, node, moduleName) { + // Handle Ember.X references to classes + if ( + types.isCallExpression(node) && + types.isMemberExpression(node.callee) && + types.isMemberExpression(node.callee.object) && + utils.getSourceModuleName(node.callee) === 'Ember' + ) { + return node.callee.object.property.name === moduleName; + } + + let superClass; + if (types.isCallExpression(node)) { // "classic" class pattern - return isClassicEmberCoreModule(node, moduleName, context.getFilename()); + superClass = node.callee.object; } else if (types.isClassDeclaration(node)) { // native classes - if (!node.superClass) { - return false; - } - const superClassImportPath = getSourceModuleNameForIdentifier(context, node.superClass); - - if (superClassImportPath === CORE_MODULE_IMPORT_PATHS[moduleName]) { - return true; - } + superClass = node.superClass; } else { assert( false, 'Function should only be called on a `CallExpression` (classic class) or `ClassDeclaration` (native class)' ); } - return false; + + if (!superClass) { + return false; + } + + const superClassImportPath = getSourceModuleNameForIdentifier(context, superClass); + + return ( + superClassImportPath === CORE_MODULE_IMPORT_PATHS[moduleName] && + isDefaultImport(context, superClass) + ); } function isEmberComponent(context, node) { diff --git a/tests/lib/utils/ember-test.js b/tests/lib/utils/ember-test.js index 9f0681e4a6..d664ebca41 100644 --- a/tests/lib/utils/ember-test.js +++ b/tests/lib/utils/ember-test.js @@ -607,12 +607,6 @@ describe('isEmberProxy', () => { const node = context.ast.body[1]; expect(emberUtils.isEmberProxy(context, node)).toBeTruthy(); }); - - it('should not detect random code', () => { - const context = new FauxContext('someFunctionCall();'); - const node = context.ast.body[0].expression; - expect(emberUtils.isEmberProxy(context, node)).toBeFalsy(); - }); }); describe('isInjectedServiceProp', () => { From ba714c532e76447413922bb3aba0f98c9fd8f4b4 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 20 Nov 2019 20:14:40 -0500 Subject: [PATCH 6/6] test(require-tagless-components): add Service tests Adding tests to validate #601 --- tests/lib/rules/require-tagless-components.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/lib/rules/require-tagless-components.js b/tests/lib/rules/require-tagless-components.js index e9c9496b40..66c7fb8239 100644 --- a/tests/lib/rules/require-tagless-components.js +++ b/tests/lib/rules/require-tagless-components.js @@ -56,6 +56,14 @@ ruleTester.run('require-tagless-components', rule, { tagName = 'some-non-empty-value'; } `, + ` + import Service from '@ember/service'; + export default Service.extend({}); + `, + ` + import Service from '@ember/service'; + export default class MyService extends Service {} + `, ], invalid: [ {