From 758d8a972e402e0495469ed54e21c8bf7fe61ea6 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Fri, 19 Jul 2024 13:46:19 +0200 Subject: [PATCH 1/9] feat(packages/eslint-plugin-sui): create linter rule --- packages/eslint-plugin-sui/src/index.js | 4 +- .../src/rules/private-attributes-model.js | 90 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin-sui/src/rules/private-attributes-model.js diff --git a/packages/eslint-plugin-sui/src/index.js b/packages/eslint-plugin-sui/src/index.js index 1620724c4..ff04f4330 100644 --- a/packages/eslint-plugin-sui/src/index.js +++ b/packages/eslint-plugin-sui/src/index.js @@ -3,6 +3,7 @@ const SerializeDeserialize = require('./rules/serialize-deserialize.js') const CommonJS = require('./rules/commonjs.js') const Decorators = require('./rules/decorators.js') const LayersArch = require('./rules/layers-architecture.js') +const PrivateAttributesModel = require('./rules/private-attributes-model.js') // ------------------------------------------------------------------------------ // Plugin Definition @@ -15,6 +16,7 @@ module.exports = { 'serialize-deserialize': SerializeDeserialize, commonjs: CommonJS, decorators: Decorators, - 'layers-arch': LayersArch + 'layers-arch': LayersArch, + 'private-attributes-model': PrivateAttributesModel } } diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js new file mode 100644 index 000000000..a5e8b2613 --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -0,0 +1,90 @@ +/** + * @fileoverview ensure domain model have a private attributes and each attribute has a getter + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'Ensure domain models have all attributes as private and each attribute has a getter', + recommended: false, + url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements' + }, + fixable: null, + schema: [], + messages: { + attributeHasToBePrivate: dedent` + If your class is a domain model (Value Object or Entity), you have to define all attributes as private. + `, + privateAttributeHasToHaveGetter: dedent` + If your class is a domain model (Value Object or Entity), you have to define a getter for the attribute #{{attributeName}}. + You can define a native getter (get {{attributeName}}) or a custom getter ({{customGetterName}}). + ` + } + }, + + create(context) { + return { + ClassDeclaration(node) { + const className = node?.id?.name ?? '' + + const allowedWords = ['VO', 'ValueObject', 'Entity'] + + const isDomainModel = allowedWords.some(allowWord => className.includes(allowWord)) + + if (!isDomainModel) return // eslint-disable-line + + // Check if all attributes are public + const publicAttributes = node.body.body.filter(node => { + return node.type === 'PropertyDefinition' && node.key.type === 'Identifier' + }) + + publicAttributes.forEach(attribute => { + context.report({ + node: attribute, + messageId: 'attributeHasToBePrivate' + }) + }) + + // Check if a private attribute has a public accessor + const privateAttributes = node.body.body.filter(node => { + return node.type === 'PropertyDefinition' && node.key.type === 'PrivateIdentifier' + }) + const classMethods = node.body.body.filter(node => node.type === 'MethodDefinition') + + privateAttributes.forEach(attribute => { + let hasGetter = false + const customGetterName = `get${attribute.key.name.charAt(0).toUpperCase()}${attribute.key.name.slice(1)}` + + classMethods.forEach(method => { + const existNativeGetterWithAttributeKey = method.key.name === attribute.key.name && method.kind === 'get' + const existCustomGetterWithAttributeKey = method.key.name === customGetterName + + if (existNativeGetterWithAttributeKey | existCustomGetterWithAttributeKey) { + hasGetter = true + } + }) + + if (!hasGetter) { + context.report({ + node: attribute, + messageId: 'privateAttributeHasToHaveGetter', + data: { + attributeName: attribute.key.name, + customGetterName + } + }) + } + }) + } + } + } +} From 37e382f2783e560f13fafbf9d9a861dbd21b9a9d Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Fri, 19 Jul 2024 13:46:39 +0200 Subject: [PATCH 2/9] feat(packages/sui-lint): add new rule to sui config --- packages/sui-lint/eslintrc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sui-lint/eslintrc.js b/packages/sui-lint/eslintrc.js index 06787f9b6..bada9b267 100644 --- a/packages/sui-lint/eslintrc.js +++ b/packages/sui-lint/eslintrc.js @@ -239,7 +239,8 @@ module.exports = { rules: { 'sui/factory-pattern': RULES.WARNING, 'sui/serialize-deserialize': RULES.WARNING, - 'sui/decorators': RULES.WARNING + 'sui/decorators': RULES.WARNING, + 'sui/private-attributes-model': RULES.WARNING } }, { From 1af4c667f7349fe7dbca2305a11b6013374e2426 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Mon, 22 Jul 2024 09:09:27 +0200 Subject: [PATCH 3/9] feat(packages/eslint-plugin-sui): typo --- .../eslint-plugin-sui/src/rules/private-attributes-model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index a5e8b2613..72627de3e 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -68,7 +68,7 @@ module.exports = { const existNativeGetterWithAttributeKey = method.key.name === attribute.key.name && method.kind === 'get' const existCustomGetterWithAttributeKey = method.key.name === customGetterName - if (existNativeGetterWithAttributeKey | existCustomGetterWithAttributeKey) { + if (existNativeGetterWithAttributeKey || existCustomGetterWithAttributeKey) { hasGetter = true } }) From 4dba34111a548887c6e6ea9ce65382bb4ec7f771 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Mon, 22 Jul 2024 09:13:33 +0200 Subject: [PATCH 4/9] feat(packages/eslint-plugin-sui): change private attributes message --- .../eslint-plugin-sui/src/rules/private-attributes-model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 72627de3e..7f2743f58 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -22,7 +22,7 @@ module.exports = { schema: [], messages: { attributeHasToBePrivate: dedent` - If your class is a domain model (Value Object or Entity), you have to define all attributes as private. + If your class is a domain model (Value Object or Entity), you have to define all attributes as private with #. `, privateAttributeHasToHaveGetter: dedent` If your class is a domain model (Value Object or Entity), you have to define a getter for the attribute #{{attributeName}}. From 3cd13fee3e6b5f3b41f7f986c563b2623b044a80 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Mon, 22 Jul 2024 09:45:04 +0200 Subject: [PATCH 5/9] feat(packages/eslint-plugin-sui): check if parents folder are valueObject or entity --- .../src/rules/private-attributes-model.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 7f2743f58..9b97fcb30 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -4,6 +4,7 @@ 'use strict' const dedent = require('string-dedent') +const path = require('path') // ------------------------------------------------------------------------------ // Rule Definition @@ -32,15 +33,23 @@ module.exports = { }, create(context) { + const filePath = context.getFilename() + const relativePath = path.relative(context.getCwd(), filePath) + + // Check if the file is inside requierd folders (useCases, services, repositories, ...) + const valueObjectPattern = /valueObjects|valueobjects|ValueObjects|Valueobjects/i + const isValueObjectPath = valueObjectPattern.test(relativePath) + + const entityPattern = /entity|Entity/i + const isEntityPath = entityPattern.test(relativePath) + return { ClassDeclaration(node) { const className = node?.id?.name ?? '' - const allowedWords = ['VO', 'ValueObject', 'Entity'] + const isDomainModelName = allowedWords.some(allowWord => className.includes(allowWord)) - const isDomainModel = allowedWords.some(allowWord => className.includes(allowWord)) - - if (!isDomainModel) return // eslint-disable-line + if (!isDomainModelName || !isValueObjectPath || !isEntityPath) return // eslint-disable-line // Check if all attributes are public const publicAttributes = node.body.body.filter(node => { From 6310ab2bd4b008ee712517b6be20acb31dcf72a6 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Mon, 22 Jul 2024 10:39:58 +0200 Subject: [PATCH 6/9] feat(packages/eslint-plugin-sui): check if arrow function as a getter --- .../src/rules/private-attributes-model.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 9b97fcb30..960b93b59 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -49,11 +49,12 @@ module.exports = { const allowedWords = ['VO', 'ValueObject', 'Entity'] const isDomainModelName = allowedWords.some(allowWord => className.includes(allowWord)) - if (!isDomainModelName || !isValueObjectPath || !isEntityPath) return // eslint-disable-line + if (!isDomainModelName && !isValueObjectPath) return + if (!isDomainModelName && !isEntityPath) return // Check if all attributes are public const publicAttributes = node.body.body.filter(node => { - return node.type === 'PropertyDefinition' && node.key.type === 'Identifier' + return node.type === 'PropertyDefinition' && node.key.type === 'Identifier' && node.value?.type !== 'ArrowFunctionExpression' }) publicAttributes.forEach(attribute => { @@ -67,7 +68,9 @@ module.exports = { const privateAttributes = node.body.body.filter(node => { return node.type === 'PropertyDefinition' && node.key.type === 'PrivateIdentifier' }) - const classMethods = node.body.body.filter(node => node.type === 'MethodDefinition') + const classMethods = node.body.body.filter(node => { + return node.type === 'MethodDefinition' || node.value?.type === 'ArrowFunctionExpression' + }) privateAttributes.forEach(attribute => { let hasGetter = false From c5b49a27daafdb7d8b8fca28b68a8b1c41df2ad4 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Mon, 22 Jul 2024 10:40:11 +0200 Subject: [PATCH 7/9] feat(packages/eslint-plugin-sui): check if arrow function as a getter --- .../eslint-plugin-sui/src/rules/private-attributes-model.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 960b93b59..4d65a41e3 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -54,7 +54,11 @@ module.exports = { // Check if all attributes are public const publicAttributes = node.body.body.filter(node => { - return node.type === 'PropertyDefinition' && node.key.type === 'Identifier' && node.value?.type !== 'ArrowFunctionExpression' + return ( + node.type === 'PropertyDefinition' && + node.key.type === 'Identifier' && + node.value?.type !== 'ArrowFunctionExpression' + ) }) publicAttributes.forEach(attribute => { From 7ff536ffc6cb936d0d10f48c0a4595c58f089f4a Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Tue, 23 Jul 2024 09:43:04 +0200 Subject: [PATCH 8/9] feat(packages/eslint-plugin-sui): check if keys exists --- .../src/rules/private-attributes-model.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 4d65a41e3..4d1423519 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -45,7 +45,7 @@ module.exports = { return { ClassDeclaration(node) { - const className = node?.id?.name ?? '' + const className = node.id?.name ?? '' const allowedWords = ['VO', 'ValueObject', 'Entity'] const isDomainModelName = allowedWords.some(allowWord => className.includes(allowWord)) @@ -53,10 +53,10 @@ module.exports = { if (!isDomainModelName && !isEntityPath) return // Check if all attributes are public - const publicAttributes = node.body.body.filter(node => { + const publicAttributes = node?.body?.body?.filter(node => { return ( node.type === 'PropertyDefinition' && - node.key.type === 'Identifier' && + node.key?.type === 'Identifier' && node.value?.type !== 'ArrowFunctionExpression' ) }) @@ -70,7 +70,7 @@ module.exports = { // Check if a private attribute has a public accessor const privateAttributes = node.body.body.filter(node => { - return node.type === 'PropertyDefinition' && node.key.type === 'PrivateIdentifier' + return node.type === 'PropertyDefinition' && node.key?.type === 'PrivateIdentifier' }) const classMethods = node.body.body.filter(node => { return node.type === 'MethodDefinition' || node.value?.type === 'ArrowFunctionExpression' @@ -81,8 +81,8 @@ module.exports = { const customGetterName = `get${attribute.key.name.charAt(0).toUpperCase()}${attribute.key.name.slice(1)}` classMethods.forEach(method => { - const existNativeGetterWithAttributeKey = method.key.name === attribute.key.name && method.kind === 'get' - const existCustomGetterWithAttributeKey = method.key.name === customGetterName + const existNativeGetterWithAttributeKey = method?.key?.name === attribute?.key?.name && method?.kind === 'get' + const existCustomGetterWithAttributeKey = method?.key?.name === customGetterName if (existNativeGetterWithAttributeKey || existCustomGetterWithAttributeKey) { hasGetter = true @@ -94,7 +94,7 @@ module.exports = { node: attribute, messageId: 'privateAttributeHasToHaveGetter', data: { - attributeName: attribute.key.name, + attributeName: attribute?.key?.name, customGetterName } }) From 4fbb929d3b360a648a280e56a33fb6d6b8475fb0 Mon Sep 17 00:00:00 2001 From: aleix-ferrer Date: Tue, 23 Jul 2024 09:43:33 +0200 Subject: [PATCH 9/9] feat(packages/eslint-plugin-sui): check if keys exists --- .../eslint-plugin-sui/src/rules/private-attributes-model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js index 4d1423519..d9772db21 100644 --- a/packages/eslint-plugin-sui/src/rules/private-attributes-model.js +++ b/packages/eslint-plugin-sui/src/rules/private-attributes-model.js @@ -81,7 +81,8 @@ module.exports = { const customGetterName = `get${attribute.key.name.charAt(0).toUpperCase()}${attribute.key.name.slice(1)}` classMethods.forEach(method => { - const existNativeGetterWithAttributeKey = method?.key?.name === attribute?.key?.name && method?.kind === 'get' + const existNativeGetterWithAttributeKey = + method?.key?.name === attribute?.key?.name && method?.kind === 'get' const existCustomGetterWithAttributeKey = method?.key?.name === customGetterName if (existNativeGetterWithAttributeKey || existCustomGetterWithAttributeKey) {