Skip to content

Commit

Permalink
fix: crash with this.extend() in no-classic-classes rule
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Mar 22, 2021
1 parent 7a65db3 commit 005d045
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 1 deletion.
7 changes: 6 additions & 1 deletion lib/rules/no-classic-classes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const { getSourceModuleNameForIdentifier } = require('../utils/import');
const { startsWithThisExpression } = require('../utils/utils');
const { isObjectExpression } = require('../utils/types');

const ERROR_MESSAGE_NO_CLASSIC_CLASSES =
Expand Down Expand Up @@ -77,7 +78,11 @@ module.exports = {
// Invalid if there are no arguments because that is equivalent to not called `extend` at all
// Invalid with an object as an argument because that logic needs conversion to a native class
// Still allows `.extend` if some other identifier is passed, like a Mixin
if (hasNoArguments(callExpression) || hasObjectArgument(callExpression)) {
if (
(hasNoArguments(callExpression) || hasObjectArgument(callExpression)) &&
['Identifier', 'MemberExpression'].includes(node.object.type) &&
!startsWithThisExpression(node)
) {
const classImportedFrom = getSourceModuleNameForIdentifier(context, node.object);
if (
isEmberImport(classImportedFrom) ||
Expand Down
15 changes: 15 additions & 0 deletions lib/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
isObjectPattern,
isOptionalCallExpression,
isOptionalMemberExpression,
isThisExpression,
} = require('../utils/types');

module.exports = {
Expand All @@ -24,6 +25,7 @@ module.exports = {
isInLeftSideOfAssignmentExpression,
parseArgs,
parseCallee,
startsWithThisExpression,
};

/**
Expand Down Expand Up @@ -250,3 +252,16 @@ function isInLeftSideOfAssignmentExpression(node) {
)
);
}

function startsWithThisExpression(node) {
if (isCallExpression(node) && node.callee) {
return startsWithThisExpression(node.callee);
} else if (isMemberExpression(node) && node.object) {
return startsWithThisExpression(node.object);
} else if (isIdentifier(node)) {
return false;
} else if (isThisExpression(node)) {
return true;
}
return false;
}
4 changes: 4 additions & 0 deletions tests/lib/rules/no-classic-classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ ruleTester.run('no-classic-classes', rule, {
export default SomeOtherThing.extend({});
`,
'export default Component.extend({});', // No import

'this.extend();',
'this.foo.extend();',
'this.foo.bar.extend();',
],

invalid: [
Expand Down
15 changes: 15 additions & 0 deletions tests/lib/utils/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,18 @@ describe('parseCallee', () => {
expect(parsedCallee).toStrictEqual(['Ember', 'A']);
});
});

describe('startsWithThisExpression', () => {
it('should behave correctly', () => {
expect(utils.startsWithThisExpression(parse('x'))).toBeFalsy();
expect(utils.startsWithThisExpression(parse('x.y'))).toBeFalsy();
expect(utils.startsWithThisExpression(parse('x()'))).toBeFalsy();
expect(utils.startsWithThisExpression(parse('x.y()'))).toBeFalsy();
expect(utils.startsWithThisExpression(parse('x.y.z()'))).toBeFalsy();
expect(utils.startsWithThisExpression(parse('this'))).toBeTruthy();
expect(utils.startsWithThisExpression(parse('this.x'))).toBeTruthy();
expect(utils.startsWithThisExpression(parse('this.x.y'))).toBeTruthy();
expect(utils.startsWithThisExpression(parse('this.x()'))).toBeTruthy();
expect(utils.startsWithThisExpression(parse('this.x.y()'))).toBeTruthy();
});
});

0 comments on commit 005d045

Please sign in to comment.