Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(packages/sui-decorators): Create AsyncInlineError decorator #1746

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
bc73cff
feat(packages/sui-decorators): Create AsyncInlineError decorator
oriolpuig Mar 27, 2024
2b89c71
feat(packages/eslint-plugin-sui): Decorators ADR: validate AsyncInlin…
oriolpuig May 29, 2024
5ea3a37
feat(packages/eslint-plugin-sui): Decorators ADR: AsyncInlineError mu…
oriolpuig May 29, 2024
2a184b6
feat(packages/eslint-plugin-sui): Decorators ADR: @AsyncInlineError m…
oriolpuig May 29, 2024
9a44253
feat(packages/eslint-plugin-sui): Decorators ADR: check decorators or…
oriolpuig May 29, 2024
f3a2001
feat(packages/eslint-plugin-sui): [ADR] Decorators: Use @AsyncInlineE…
oriolpuig Jun 6, 2024
d830f2b
feat(packages/eslint-plugin-sui): ADR Decorators: use MethodDefinitio…
oriolpuig Jun 12, 2024
4bab675
feat(packages/eslint-plugin-sui): Rename rule to decorator-async-inli…
oriolpuig Jul 2, 2024
6294dd8
feat(packages/sui-lint): Add sui/decorator-async-inline-error rule to…
oriolpuig Jul 2, 2024
fe8b7f5
chore(Root): Update package-lock
oriolpuig Jul 2, 2024
bee8cb2
docs(packages/sui-decorators): Add documentation for new @AsyncInline…
oriolpuig Jul 2, 2024
d997ff7
feat(packages/sui-decorators): @inlineError - Add warning to avoid us…
oriolpuig Jul 2, 2024
6124f6b
feat(packages/eslint-plugin-sui): Create new rule for decorator-inlin…
oriolpuig Jul 2, 2024
3d7d977
feat(packages/sui-lint): Add new decorator-inline-error to domain pac…
oriolpuig Jul 2, 2024
163a959
feat(packages/eslint-plugin-sui): @inlineError must be on non-async f…
oriolpuig Jul 3, 2024
ef3c679
refactor(packages/eslint-plugin-sui): Clean @AsyncInlineError decorat…
oriolpuig Jul 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/eslint-plugin-sui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const FactoryPattern = require('./rules/factory-pattern.js')
const SerializeDeserialize = require('./rules/serialize-deserialize.js')
const CommonJS = require('./rules/commonjs.js')
const Decorators = require('./rules/decorators.js')
const DecoratorAsyncInlineError = require('./rules/decorator-async-inline-error.js')
const DecoratorInlineError = require('./rules/decorator-inline-error.js')
const LayersArch = require('./rules/layers-architecture.js')

// ------------------------------------------------------------------------------
Expand All @@ -15,6 +17,8 @@ module.exports = {
'serialize-deserialize': SerializeDeserialize,
commonjs: CommonJS,
decorators: Decorators,
'decorator-async-inline-error': DecoratorAsyncInlineError,
'decorator-inline-error': DecoratorInlineError,
'layers-arch': LayersArch
}
}
137 changes: 137 additions & 0 deletions packages/eslint-plugin-sui/src/rules/decorator-async-inline-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* @fileoverview Ensure that at least all your UseCases, Services and Repositories are using @AsyncInlineError decorator from sui
*/
'use strict'

const dedent = require('string-dedent')
const path = require('path')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Ensure that at least all your UseCases, Services and Repositories are using @AsyncInlineError decorator from sui',
recommended: true,
url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements'
},
fixable: 'code',
schema: [],
messages: {
notFoundAsyncInlineErrorDecoratorOnUseCase: dedent`
The execute method of a UseCase should use the @AsyncInlineError() decorator in order to follow the Adevinta domain code guidelines.
`,
notFoundAsyncInlineErrorDecoratorOnService: dedent`
The execute method of a Service should use the @AsyncInlineError() decorator in order to follow the Adevinta domain code guidelines.
`,
notFoundAsyncInlineErrorDecoratorOnRepository: dedent`
The public Repository methods should use the @AsyncInlineError() decorator in order to follow the Adevinta domain code guidelines.
`,
asyncInlineErrorDecoratorIsNotFirst: dedent`
The @AsyncInlineError() decorator must always be closest to the execute method to avoid inconsistence with other decorators.
`
}
},
create: function (context) {
const filePath = context.getFilename()
const relativePath = path.relative(context.getCwd(), filePath)

// Check if the file is inside requierd folders (useCases, services, repositories, ...)
const useCasePattern = /useCases|usecases/i
const isUseCasePath = useCasePattern.test(relativePath)

const servicePattern = /services/i
const isServicePath = servicePattern.test(relativePath)

const repositoryPattern = /repositories/i
const isRepositoryPath = repositoryPattern.test(relativePath)

return {
MethodDefinition(node) {
// Method
const method = node
const methodName = method.key?.name
const isExecuteMethod = methodName === 'execute'

// Class
const classObject = node.parent?.parent
const className = classObject?.id?.name
const superClassName = classObject?.superClass?.name

// UseCase
const containUseCase = className?.endsWith('UseCase')
const extendsUseCase = superClassName === 'UseCase'
const isUsecase = containUseCase || extendsUseCase || isUseCasePath

// Service
const containService = className?.endsWith('Service')
const extendsService = superClassName === 'Service'
const isService = containService || extendsService || isServicePath

// Repository
const containRepository = className?.endsWith('Repository')
const extendsRepository = superClassName === 'Repository'
const isRepository = containRepository || extendsRepository || isRepositoryPath

// Skip if it's not a UseCase, Service or Repository
if (!isUsecase && !isService && !isRepository && !isExecuteMethod) return

// Skip if a constructor or a not public method (starts by _ or #)
if (methodName === 'constructor') return
if (methodName.startsWith('_')) return
if (methodName.startsWith('#')) return
if ((isUsecase || isService) && !isExecuteMethod) return

// Method decorators
const methodDecorators = method.decorators
const hasDecorators = methodDecorators?.length > 0

// Get the @AsyncInlineError decorator from method
const asyncInlineErrorDecoratorNode =
hasDecorators &&
methodDecorators?.find(decorator => decorator?.expression?.callee?.name === 'AsyncInlineError')

// Check if the @AsyncInlineError decorator is the last one
const isAsyncInlineErrorLastDecorator =
hasDecorators && methodDecorators?.at(-1)?.expression?.callee?.name === 'AsyncInlineError'

// RULE: The method should have the @AsyncInlineError decorator
if (!asyncInlineErrorDecoratorNode && isUsecase) {
context.report({
node: method.key,
messageId: 'notFoundAsyncInlineErrorDecoratorOnUseCase'
})
}
if (!asyncInlineErrorDecoratorNode && isService) {
context.report({
node: method.key,
messageId: 'notFoundAsyncInlineErrorDecoratorOnService'
})
}
if (!asyncInlineErrorDecoratorNode && isRepository) {
context.report({
node: method.key,
messageId: 'notFoundAsyncInlineErrorDecoratorOnRepository'
})
}

// RULE: The @AsyncInlineError decorator should be the first one, to avoid inconsistencies with other decorators
if (asyncInlineErrorDecoratorNode && !isAsyncInlineErrorLastDecorator) {
context.report({
node: asyncInlineErrorDecoratorNode,
messageId: 'asyncInlineErrorDecoratorIsNotFirst',
*fix(fixer) {
yield fixer.remove(asyncInlineErrorDecoratorNode)
yield fixer.insertTextAfter(methodDecorators.at(-1), '\n@AsyncInlineError()')
}
})
}
}
}
}
}
139 changes: 139 additions & 0 deletions packages/eslint-plugin-sui/src/rules/decorator-inline-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* @fileoverview Ensure the right usage of @inlineError decorator from sui in sui-domain
*/
'use strict'

const dedent = require('string-dedent')
const path = require('path')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Ensure the right usage of @inlineError decorator from sui in sui-domain',
recommended: true,
url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements'
},
fixable: 'code',
schema: [],
messages: {
avoidUseInlineErrorOnAsyncFunctions: dedent`
The @inlineError decorator is deprecated on async functions. Use the @AsyncInlineError() decorator instead.
`,
useInlineErrorOnNonAsyncFunctions: dedent`
The @inlineError decorator should be used on non async functions.
`,
inlineErrorDecoratorIsNotFirst: dedent`
The @inlineError decorator must always be closest to the execute method to avoid inconsistence with other decorators.
`
}
},
create: function (context) {
const asyncInlineErrorImportStatement = "import {AsyncInlineError} from '@s-ui/decorators';\n"

const filePath = context.getFilename()
const relativePath = path.relative(context.getCwd(), filePath)

// Check if the file is inside requierd folders (useCases, services, repositories, ...)
const useCasePattern = /useCases|usecases/i
const isUseCasePath = useCasePattern.test(relativePath)

const servicePattern = /services/i
const isServicePath = servicePattern.test(relativePath)

const repositoryPattern = /repositories/i
const isRepositoryPath = repositoryPattern.test(relativePath)

return {
MethodDefinition(node) {
// Method
const method = node
const isAsync = method?.value?.async || false
const methodName = method.key?.name
const isExecuteMethod = methodName === 'execute'

// Class
const classObject = node.parent?.parent
const className = classObject?.id?.name
const superClassName = classObject?.superClass?.name

// UseCase
const containUseCase = className?.endsWith('UseCase')
const extendsUseCase = superClassName === 'UseCase'
const isUsecase = containUseCase || extendsUseCase || isUseCasePath

// Service
const containService = className?.endsWith('Service')
const extendsService = superClassName === 'Service'
const isService = containService || extendsService || isServicePath

// Repository
const containRepository = className?.endsWith('Repository')
const extendsRepository = superClassName === 'Repository'
const isRepository = containRepository || extendsRepository || isRepositoryPath

// Skip if it's not a UseCase, Service or Repository
if (!isUsecase && !isService && !isRepository && !isExecuteMethod) return

// Skip if a constructor or a not public method (starts by _ or #)
if (methodName === 'constructor') return
if (methodName.startsWith('_')) return
if (methodName.startsWith('#')) return
if ((isUsecase || isService) && !isExecuteMethod) return

// Method decorators
const methodDecorators = method.decorators
const hasDecorators = methodDecorators?.length > 0

// Get the @inlineError decorator from method
const inlineErrorDecoratorNode =
hasDecorators && methodDecorators?.find(decorator => decorator?.expression?.name === 'inlineError')

// Check if the @inlineError decorator is the last one
const isInlineErrorLastDecorator = hasDecorators && methodDecorators?.at(-1)?.expression?.name === 'inlineError'

// TODO: Pending to check if a function is returning a promise (not using async/await syntax)
// RULE: An async function MUST use the @AsyncInlineError() decorator
if (inlineErrorDecoratorNode && isAsync) {
context.report({
node: inlineErrorDecoratorNode,
messageId: 'avoidUseInlineErrorOnAsyncFunctions',
*fix(fixer) {
yield fixer.remove(inlineErrorDecoratorNode)
yield fixer.insertTextAfter(methodDecorators.at(-1), '\n@AsyncInlineError()')
yield fixer.insertTextBeforeRange([0, 0], asyncInlineErrorImportStatement)
}
})
}

// @inlineError decorator should be used on non async functions
if (!isAsync) {
// RULE: A non-async function should use the @inlineError decorator should be the first one
if (!inlineErrorDecoratorNode) {
context.report({
node: method.key,
messageId: 'useInlineErrorOnNonAsyncFunctions'
})
}

// RULE: The @inlineError decorator should be the first one, to avoid inconsistencies with other decorators.
if (inlineErrorDecoratorNode && !isInlineErrorLastDecorator) {
context.report({
node: inlineErrorDecoratorNode,
messageId: 'inlineErrorDecoratorIsNotFirst',
*fix(fixer) {
yield fixer.remove(inlineErrorDecoratorNode)
yield fixer.insertTextAfter(methodDecorators.at(-1), '\n@inlineError')
}
})
}
}
}
}
}
}
Loading