eslint-plugin-ember
Advanced tools
Comparing version 4.2.0 to 4.3.0
@@ -41,2 +41,9 @@ ## Use named functions defined on objects to handle promises | ||
}, | ||
// GOOD if allowSimpleArrowFunction: true | ||
updateUser(user) { | ||
user.save() | ||
.then(() => this._reloadUser()) | ||
.then(() => this._notifyAboutSuccess()) | ||
.catch(() => this._notifyAboutFailure()); | ||
}, | ||
}, | ||
@@ -43,0 +50,0 @@ _reloadUser(user) { |
@@ -28,2 +28,3 @@ /* | ||
"ember/order-in-routes": "error", | ||
"ember/require-super-in-init": "off", | ||
"ember/routes-segments-snake-case": "error", | ||
@@ -30,0 +31,0 @@ "ember/use-brace-expansion": "error", |
@@ -31,13 +31,29 @@ 'use strict'; | ||
const properties = node.arguments | ||
.filter(arg => utils.isLiteral(arg) && typeof arg.value === 'string') | ||
const matchesBraces = x => !!x.match(/[{}]/g); | ||
const hasBraces = arr => arr.some(matchesBraces); | ||
const beforeBraces = arr => arr.slice(0, arr.indexOf(arr.find(matchesBraces))); | ||
const arrayDeepEqual = (a, b) => | ||
a.length === b.length && a.reduce((acc, e, i) => acc && e === b[i], true); | ||
const problem = node.arguments | ||
.filter(arg => utils.isLiteral(arg) && typeof arg.value === 'string' && arg.value.indexOf('.') >= 0) | ||
.map(e => e.value.split('.')) | ||
.filter(e => e.length > 1) | ||
.map(e => e[0]) | ||
.sort((a, b) => a > b); | ||
.find((prop, i, props) => props.filter((e) => { | ||
const propHasBraces = hasBraces(prop); | ||
const eHasBraces = hasBraces(e); | ||
for (let i = 0; i < properties.length - 1; i++) { | ||
if (properties[i] === properties[i + 1]) { | ||
report(node); | ||
} | ||
if (propHasBraces && eHasBraces) { | ||
return arrayDeepEqual(beforeBraces(e), beforeBraces(prop)); | ||
} else if (!propHasBraces && !eHasBraces) { | ||
return prop[0] === e[0]; | ||
} | ||
const withBraces = propHasBraces ? prop : e; | ||
const withoutBraces = propHasBraces ? e : prop; | ||
const shareable = beforeBraces(withBraces); | ||
return arrayDeepEqual(shareable, withoutBraces.slice(0, shareable.length)); | ||
}).length > 1); | ||
if (problem) { | ||
report(node); | ||
} | ||
@@ -44,0 +60,0 @@ }, |
@@ -14,2 +14,3 @@ 'use strict'; | ||
isEmberRoute, | ||
isEmberMixin, | ||
@@ -47,2 +48,5 @@ isSingleLineFn, | ||
isControllerDefaultProp, | ||
parseDependentKeys, | ||
unwrapBraceExpressions, | ||
hasDuplicateDependentKeys, | ||
}; | ||
@@ -125,2 +129,5 @@ | ||
} | ||
function isEmberMixin(node, filePath) { | ||
return isEmberCoreModule(node, 'Mixin', filePath); | ||
} | ||
@@ -370,1 +377,66 @@ function isInjectedServiceProp(node) { | ||
} | ||
/** | ||
* Checks whether a computed property has duplicate dependent keys. | ||
* | ||
* @param {CallExpression} callExp Given call expression | ||
* @return {Boolean} Flag whether dependent keys present. | ||
*/ | ||
function hasDuplicateDependentKeys(callExp) { | ||
if (!isComputedProp(callExp)) return false; | ||
const dependentKeys = parseDependentKeys(callExp); | ||
const uniqueKeys = dependentKeys | ||
.filter((val, index, self) => self.indexOf(val) === index); | ||
return uniqueKeys.length !== dependentKeys.length; | ||
} | ||
/** | ||
* Parses dependent keys from call expression and returns them in an array. | ||
* | ||
* It also unwraps the expressions, so that `model.{foo,bar}` becomes `model.foo, model.bar`. | ||
* | ||
* @param {CallExpression} callExp CallExpression to examine | ||
* @return {Array} Array of unwrapped dependent keys | ||
*/ | ||
function parseDependentKeys(callExp) { | ||
// Check whether we have a MemberExpression, eg. computed(...).volatile() | ||
const isMemberExpCallExp = !callExp.arguments.length && | ||
utils.isMemberExpression(callExp.callee) && | ||
utils.isCallExpression(callExp.callee.object); | ||
const args = isMemberExpCallExp ? callExp.callee.object.arguments : callExp.arguments; | ||
const dependentKeys = args | ||
.filter(arg => utils.isLiteral(arg)) | ||
.map(literal => literal.value); | ||
return unwrapBraceExpressions(dependentKeys); | ||
} | ||
/** | ||
* Unwraps brace expressions. | ||
* | ||
* @param {Array} dependentKeys array of strings containing unprocessed dependent keys. | ||
* @return {Array} Array of unwrapped dependent keys | ||
*/ | ||
function unwrapBraceExpressions(dependentKeys) { | ||
const braceExpressionRegexp = /{.+}/g; | ||
const unwrappedExpressions = dependentKeys.map((key) => { | ||
if (!braceExpressionRegexp.test(key)) return key; | ||
const braceExpansionPart = key.match(braceExpressionRegexp)[0]; | ||
const prefix = key.replace(braceExpansionPart, ''); | ||
const properties = braceExpansionPart | ||
.replace('{', '') | ||
.replace('}', '') | ||
.split(','); | ||
return properties.map(property => `${prefix}${property}`); | ||
}); | ||
return unwrappedExpressions | ||
.reduce((acc, cur) => acc.concat(cur), []); | ||
} |
{ | ||
"name": "eslint-plugin-ember", | ||
"version": "4.2.0", | ||
"version": "4.3.0", | ||
"description": "Eslint plugin for Ember.js apps", | ||
@@ -56,3 +56,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"ember-rfc176-data": "^0.2.2", | ||
"ember-rfc176-data": "^0.2.7", | ||
"requireindex": "^1.1.0", | ||
@@ -65,6 +65,3 @@ "snake-case": "^2.1.0" | ||
] | ||
}, | ||
"publishConfig": { | ||
"tag": "latest" | ||
} | ||
} |
@@ -40,4 +40,4 @@ # eslint-plugin-ember | ||
Possible configurations: | ||
- [plugin:ember/base](https://github.com/ember-cli/eslint-plugin-ember/blob/master/config/base.js) - contains only recommended settings for custom rules defined in this plugin. | ||
- [plugin:ember/recommended](https://github.com/ember-cli/eslint-plugin-ember/blob/master/config/recommended.js) - extends base configuration with extra rules' settings provided by eslint | ||
- [plugin:ember/base](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/config/base.js) - contains only recommended settings for custom rules defined in this plugin. | ||
- [plugin:ember/recommended](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/config/recommended.js) - extends base configuration with extra rules' settings provided by eslint | ||
@@ -106,2 +106,3 @@ #### Use plain plugin: | ||
| :white_check_mark: | [no-side-effects](./docs/rules/no-side-effects.md) | Warns about unexpected side effects in computed properties | | ||
| | [require-super-in-init](./docs/rules/require-super-in-init.md) | Enforces super calls in init hooks | | ||
| :white_check_mark: | [use-brace-expansion](./docs/rules/use-brace-expansion.md) | Enforces usage of brace expansion | | ||
@@ -108,0 +109,0 @@ | :white_check_mark: :wrench: | [use-ember-get-and-set](./docs/rules/use-ember-get-and-set.md) | Enforces usage of Ember.get and Ember.set | |
@@ -45,3 +45,3 @@ // ------------------------------------------------------------------------------ | ||
code: 'var Router = Ember.Router.extend({});', | ||
errors: [{ message: 'Use import Router from \'@ember/routing/router\'; instead of using Ember.Router' }], | ||
errors: [{ message: 'Use import EmberRouter from \'@ember/routing/router\'; instead of using Ember.Router' }], | ||
}, | ||
@@ -54,5 +54,5 @@ { | ||
code: 'new Ember.RSVP.Promise();', | ||
errors: [{ message: 'Use import RSVP from \'rsvp\'; instead of using Ember.RSVP' }], | ||
errors: [{ message: 'Use import { Promise } from \'rsvp\'; instead of using Ember.RSVP.Promise' }], | ||
}, | ||
], | ||
}); |
@@ -19,2 +19,5 @@ // ------------------------------------------------------------------------------ | ||
{ code: '{ test: computed("a.{test,test2}", "b", function() {}) }' }, | ||
{ code: '{ test: computed("a.{test,test2}", "c", "b", function() {}) }' }, | ||
{ code: '{ test: computed("model.a.{test,test2}", "model.b.{test3,test4}", function() {}) }' }, | ||
{ code: '{ test: computed("foo.bar.{name,place}", "foo.qux.[]", "foo.baz.{thing,@each.stuff}", function() {}) }' }, | ||
{ code: "{ test: Ember.computed.filterBy('a', 'b', false) }" }, | ||
@@ -24,2 +27,8 @@ ], | ||
{ | ||
code: '{ test: computed("foo.{name,place}", "foo.[]", "foo.{thing,@each.stuff}", function() {}) }', | ||
errors: [{ | ||
message: 'Use brace expansion', | ||
}], | ||
}, | ||
{ | ||
code: '{ test: computed("a.test", "a.test2", function() {}) }', | ||
@@ -31,2 +40,20 @@ errors: [{ | ||
{ | ||
code: '{ test: computed("a.{same,level}", "a.{not,nested}", function() {}) }', | ||
errors: [{ | ||
message: 'Use brace expansion', | ||
}], | ||
}, | ||
{ | ||
code: '{ test: computed("a.b.c.d", "a.b.deep.props", function() {}) }', | ||
errors: [{ | ||
message: 'Use brace expansion', | ||
}], | ||
}, | ||
{ | ||
code: '{ test: computed("a.{test,test2}", "c", "a.test3.test4", function() {}) }', | ||
errors: [{ | ||
message: 'Use brace expansion', | ||
}], | ||
}, | ||
{ | ||
code: '{ test: computed("a.{test,test2}", "a.test3", function() {}) }', | ||
@@ -33,0 +60,0 @@ errors: [{ |
@@ -446,2 +446,117 @@ 'use strict'; | ||
describe('parseDependentKeys', () => { | ||
it('should parse dependent keys from callexpression', () => { | ||
const node = parse("computed('model.{foo,bar}', 'model.bar')"); | ||
expect(emberUtils.parseDependentKeys(node)).toEqual([ | ||
'model.foo', 'model.bar', 'model.bar', | ||
]); | ||
}); | ||
it('should work when no dependent keys present', () => { | ||
const node = parse('computed(function() {})'); | ||
expect(emberUtils.parseDependentKeys(node)).toEqual([]); | ||
}); | ||
it('should handle dependent keys and function arguments', () => { | ||
const node = parse("computed('model.{foo,bar}', 'model.bar', function() {})"); | ||
expect(emberUtils.parseDependentKeys(node)).toEqual([ | ||
'model.foo', 'model.bar', 'model.bar', | ||
]); | ||
}); | ||
it('should handle dependent keys and function arguments in MemberExpression', () => { | ||
const node = parse(` | ||
computed('model.{foo,bar}', 'model.bar', function() { | ||
}).volatile(); | ||
`); | ||
expect(emberUtils.parseDependentKeys(node)).toEqual([ | ||
'model.foo', 'model.bar', 'model.bar', | ||
]); | ||
}); | ||
}); | ||
describe('unwrapBraceExpressions', () => { | ||
it('should unwrap simple dependent keys', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'model.foo', 'model.bar' | ||
])).toEqual(['model.foo', 'model.bar']); | ||
}); | ||
it('should unwrap dependent keys with braces', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'model.{foo,bar}', 'model.bar' | ||
])).toEqual(['model.foo', 'model.bar', 'model.bar']); | ||
}); | ||
it('should unwrap more complex dependent keys', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'model.{foo,bar}', 'model.bar', 'data.{foo,baz,qux}' | ||
])).toEqual([ | ||
'model.foo', 'model.bar', 'model.bar', 'data.foo', 'data.baz', 'data.qux', | ||
]); | ||
}); | ||
it('should unwrap multi-level keys', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'model.bar.{foo,qux}', 'model.bar.baz' | ||
])).toEqual([ | ||
'model.bar.foo', 'model.bar.qux', 'model.bar.baz' | ||
]); | ||
}); | ||
it('should unwrap @each with extensions', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'collection.@each.{foo,bar}', 'collection.@each.qux' | ||
])).toEqual([ | ||
'collection.@each.foo', 'collection.@each.bar', 'collection.@each.qux' | ||
]); | ||
}); | ||
it('should unwrap complicated mixed dependent keys', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'a.b.c.{d.@each.qwe.zxc,f,g.[]}' | ||
])).toEqual([ | ||
'a.b.c.d.@each.qwe.zxc', 'a.b.c.f', 'a.b.c.g.[]', | ||
]); | ||
}); | ||
it('should unwrap complicated mixed repeated dependent keys', () => { | ||
expect(emberUtils.unwrapBraceExpressions([ | ||
'a.b.{d.@each.qux,f,d.@each.foo}' | ||
])).toEqual([ | ||
'a.b.d.@each.qux', 'a.b.f', 'a.b.d.@each.foo', | ||
]); | ||
}); | ||
}); | ||
describe('hasDuplicateDependentKeys', () => { | ||
it('reports duplicate dependent keys in computed calls', () => { | ||
let node = parse("computed('model.{foo,bar}', 'model.bar')"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).toBeTruthy(); | ||
node = parse("Ember.computed('model.{foo,bar}', 'model.bar')"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).toBeTruthy(); | ||
}); | ||
it('ignores not repeated dependentKeys', () => { | ||
let node = parse("computed('model.{foo,bar}', 'model.qux')"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).not.toBeTruthy(); | ||
node = parse("Ember.computed('model.{foo,bar}', 'model.qux')"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).not.toBeTruthy(); | ||
node = parse("computed('model.{foo,bar}', 'model.qux').volatile()"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).not.toBeTruthy(); | ||
}); | ||
it('ignores non-computed calls', () => { | ||
const node = parse("foo('model.{foo,bar}', 'model.bar')"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).not.toBeTruthy(); | ||
}); | ||
it('reports duplicate dependent keys in computed calls with MemberExp', () => { | ||
let node = parse("Ember.computed('model.{foo,bar}', 'model.bar').volatile()"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).toBeTruthy(); | ||
node = parse("computed('model.{foo,bar}', 'model.bar').volatile()"); | ||
expect(emberUtils.hasDuplicateDependentKeys(node)).toBeTruthy(); | ||
}); | ||
}); | ||
describe('getEmberImportAliasName', () => { | ||
@@ -448,0 +563,0 @@ it('should get the proper name of default import', () => { |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
344434
99
6472
171
Updatedember-rfc176-data@^0.2.7