-
-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new ember-data rule:
require-async-inverse-relationship
(#2155)
- Loading branch information
Showing
4 changed files
with
330 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# ember/require-async-inverse-relationship | ||
|
||
<!-- end auto-generated rule header --> | ||
|
||
This rule ensures that the `async` and `inverse` properties are specified in `@belongsTo` and `@hasMany` decorators in Ember Data models. | ||
|
||
## Rule Details | ||
|
||
This rule disallows: | ||
|
||
- Using `@belongsTo` without specifying the `async` and `inverse` properties. | ||
- Using `@hasMany` without specifying the `async` and `inverse` properties. | ||
|
||
## Examples | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
import Model, { belongsTo, hasMany } from '@ember-data/model'; | ||
|
||
export default class FolderModel extends Model { | ||
@hasMany('folder', { inverse: 'parent' }) children; | ||
@belongsTo('folder', { inverse: 'children' }) parent; | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
import Model, { belongsTo, hasMany } from '@ember-data/model'; | ||
|
||
export default class FolderModel extends Model { | ||
@hasMany('folder', { async: true, inverse: 'parent' }) children; | ||
@belongsTo('folder', { async: true, inverse: 'children' }) parent; | ||
} | ||
``` | ||
|
||
## References | ||
|
||
- [Deprecate Non Strict Relationships](https://deprecations.emberjs.com/id/ember-data-deprecate-non-strict-relationships) | ||
- [Ember Data Relationships](https://guides.emberjs.com/release/models/relationships) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/** @type {import('eslint').Rule.RuleModule} */ | ||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'require inverse to be specified in @belongsTo and @hasMany decorators', | ||
category: 'Ember Data', | ||
recommended: false, | ||
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-async-inverse-relationship.md', | ||
}, | ||
schema: [], | ||
}, | ||
|
||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
const decorator = | ||
node.parent.type === 'Decorator' && | ||
['belongsTo', 'hasMany'].includes(node.callee.name) && | ||
node; | ||
|
||
if (decorator) { | ||
const args = decorator.arguments; | ||
const hasAsync = args.some( | ||
(arg) => | ||
arg.type === 'ObjectExpression' && | ||
arg.properties.some((prop) => prop.key.name === 'async') | ||
); | ||
const hasBooleanAsync = args.some( | ||
(arg) => | ||
arg.type === 'ObjectExpression' && | ||
arg.properties.some( | ||
(prop) => prop.key.name === 'async' && typeof prop.value.value === 'boolean' | ||
) | ||
); | ||
const hasInverse = args.some( | ||
(arg) => | ||
arg.type === 'ObjectExpression' && | ||
arg.properties.some((prop) => prop.key.name === 'inverse') | ||
); | ||
|
||
if (!hasAsync) { | ||
context.report({ | ||
node, | ||
message: 'The @{{decorator}} decorator requires an `async` property to be specified.', | ||
data: { | ||
decorator: decorator.callee.name, | ||
}, | ||
}); | ||
} else if (!hasBooleanAsync) { | ||
context.report({ | ||
node, | ||
message: | ||
'The @{{decorator}} decorator requires an `async` property to be specified as a boolean.', | ||
data: { | ||
decorator: decorator.callee.name, | ||
}, | ||
}); | ||
} | ||
|
||
if (!hasInverse) { | ||
context.report({ | ||
node, | ||
message: | ||
'The @{{decorator}} decorator requires an `inverse` property to be specified.', | ||
data: { | ||
decorator: decorator.callee.name, | ||
}, | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
'use strict'; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule = require('../../../lib/rules/require-async-inverse-relationship'); | ||
const RuleTester = require('eslint').RuleTester; | ||
|
||
const parserOptions = { ecmaVersion: 2022, sourceType: 'module' }; | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions, | ||
parser: require.resolve('@babel/eslint-parser'), | ||
}); | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
ruleTester.run('require-async-inverse-relationship', rule, { | ||
valid: [ | ||
`import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', { async: true, inverse: 'comments' }) post; | ||
}`, | ||
`import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment', { async: true, inverse: 'post' }) comments; | ||
}`, | ||
`import Model, { belongsTo, hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', { async: false, inverse: 'comments' }) post; | ||
@hasMany('user', { async: true, inverse: null }) owner; | ||
}`, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: `import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post') post; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @belongsTo decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @belongsTo decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', {}) post; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @belongsTo decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @belongsTo decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', { async: 'comments'}) post; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: | ||
'The @belongsTo decorator requires an `async` property to be specified as a boolean.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @belongsTo decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', { async: true }) post; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @belongsTo decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { belongsTo } from '@ember-data/model'; | ||
export default class extends Model { | ||
@belongsTo('post', { inverse: 'comments' }) post; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @belongsTo decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment') comments; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @hasMany decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @hasMany decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment', {}) comments; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @hasMany decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @hasMany decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment', { async: 'comments'}) comments; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: | ||
'The @hasMany decorator requires an `async` property to be specified as a boolean.', | ||
type: 'CallExpression', | ||
}, | ||
{ | ||
message: 'The @hasMany decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment', { async: true }) comments; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @hasMany decorator requires an `inverse` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import Model, { hasMany } from '@ember-data/model'; | ||
export default class extends Model { | ||
@hasMany('comment', { inverse: 'post' }) comments; | ||
}`, | ||
output: null, | ||
errors: [ | ||
{ | ||
message: 'The @hasMany decorator requires an `async` property to be specified.', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
], | ||
}); |