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/scmi 124866 [Domain models initiative] value attribute VO lint rule #1790

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion packages/eslint-plugin-sui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ValueAttributeVO = require('./rules/value-attribute-VO.js')

// ------------------------------------------------------------------------------
// Plugin Definition
Expand All @@ -15,6 +16,7 @@ module.exports = {
'serialize-deserialize': SerializeDeserialize,
commonjs: CommonJS,
decorators: Decorators,
'layers-arch': LayersArch
'layers-arch': LayersArch,
'value-attribute-VO': ValueAttributeVO
}
}
123 changes: 123 additions & 0 deletions packages/eslint-plugin-sui/src/rules/value-attribute-VO.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* @fileoverview ensure value object has a value attribute
*/
'use strict'

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

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

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Ensure value object has a value attribute',
recommended: false,
url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements'
},
fixable: null,
schema: [],
messages: {
missingValueAttribute: dedent`
If your class is a value object, you have to define a 'value' attribute.
`,
missingPrivateValueAttribute: dedent`
The 'value' attribute has to be private.
`,
missingValueAttributeGetter: dedent`
The 'value' attribute has to have a getter.
You can define a native getter (get {{attributeName}}) or a custom getter ({{customGetterName}}).
`
}
},

// If your class is a value object, you have to define a private 'value' attribute and the associated getter.

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

// Check if the file is inside requierd folders (useCases, services, repositories, ...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 update comment or remove it

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))

if (!isDomainModelName && !isValueObjectPath) return
if (!isDomainModelName && !isEntityPath) return

const attributes = node.body.body.filter(node => {
return node?.type === 'PropertyDefinition' && node?.value?.type !== 'ArrowFunctionExpression'
})

if (attributes.length > 1) return

// Check if exists value attribute
const valueAttribute = node?.body?.body?.find(node => {
return node?.type === 'PropertyDefinition' && node?.key?.name === 'value'
})

if (!valueAttribute) {
return context.report({
node: node?.id,
messageId: 'missingValueAttribute'
})
}

// Check if value attribute is private

const isPrivateValueAttribute = valueAttribute?.key?.type === 'PrivateIdentifier'

if (!isPrivateValueAttribute) {
return context.report({
node: node?.id,
messageId: 'missingPrivateValueAttribute'
})
}

// Check if a value attribute has a public accessor
const classMethods = node.body.body.filter(node => {
return node?.type === 'MethodDefinition' || node?.value?.type === 'ArrowFunctionExpression'
})

let hasGetter = false
const customGetterName = `get
${valueAttribute?.key?.name?.charAt(0).toUpperCase()}
${valueAttribute?.key?.name?.slice(1)}
`

classMethods.forEach(method => {
const existNativeGetterWithAttributeKey =
method?.key?.name === valueAttribute?.key?.name && method?.kind === 'get'
const existCustomGetterWithAttributeKey = method?.key?.name === customGetterName

if (existNativeGetterWithAttributeKey || existCustomGetterWithAttributeKey) {
hasGetter = true
}
})

if (!hasGetter) {
context.report({
node: valueAttribute,
messageId: 'missingValueAttributeGetter',
data: {
attributeName: valueAttribute?.key?.name,
customGetterName
}
})
}
}
}
}
}
3 changes: 2 additions & 1 deletion packages/sui-lint/eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/value-attribute-VO': RULES.WARNING
}
},
{
Expand Down