From 7e41dc7323cbe9dc782d3eb78a90fef651b72e19 Mon Sep 17 00:00:00 2001 From: Carlos Villuendas Zambrana Date: Tue, 6 Feb 2024 11:40:53 +0100 Subject: [PATCH] feat(packages/eslint-plugin-sui): create few rules --- packages/eslint-plugin-sui/README.md | 58 ++++++++++++ .../docs/rules/factory-pattern.md | 35 +++++++ packages/eslint-plugin-sui/package.json | 39 ++++++++ packages/eslint-plugin-sui/src/index.js | 14 +++ .../src/rules/factory-pattern.js | 65 +++++++++++++ .../src/rules/forbidden-require.js | 46 +++++++++ .../src/rules/naming-convention.js | 46 +++++++++ .../src/rules/serialize-deserialize.js | 93 +++++++++++++++++++ .../tests/src/rules/factory-pattern.js | 30 ++++++ 9 files changed, 426 insertions(+) create mode 100644 packages/eslint-plugin-sui/README.md create mode 100644 packages/eslint-plugin-sui/docs/rules/factory-pattern.md create mode 100644 packages/eslint-plugin-sui/package.json create mode 100644 packages/eslint-plugin-sui/src/index.js create mode 100644 packages/eslint-plugin-sui/src/rules/factory-pattern.js create mode 100644 packages/eslint-plugin-sui/src/rules/forbidden-require.js create mode 100644 packages/eslint-plugin-sui/src/rules/naming-convention.js create mode 100644 packages/eslint-plugin-sui/src/rules/serialize-deserialize.js create mode 100644 packages/eslint-plugin-sui/tests/src/rules/factory-pattern.js diff --git a/packages/eslint-plugin-sui/README.md b/packages/eslint-plugin-sui/README.md new file mode 100644 index 000000000..07481eb4d --- /dev/null +++ b/packages/eslint-plugin-sui/README.md @@ -0,0 +1,58 @@ +# eslint-plugin-sui + +Set of sui lint rules + +## Installation + +You'll first need to install [ESLint](https://eslint.org/): + +```sh +npm i eslint --save-dev +``` + +Next, install `eslint-plugin-sui`: + +```sh +npm install eslint-plugin-sui --save-dev +``` + +## Usage + +Add `sui` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: + +```json +{ + "plugins": [ + "sui" + ] +} +``` + + +Then configure the rules you want to use under the rules section. + +```json +{ + "rules": { + "sui/rule-name": 2 + } +} +``` + + + +## Configurations + + +TODO: Run eslint-doc-generator to generate the configs list (or delete this section if no configs are offered). + + + + +## Rules + + +TODO: Run eslint-doc-generator to generate the rules list. + + + diff --git a/packages/eslint-plugin-sui/docs/rules/factory-pattern.md b/packages/eslint-plugin-sui/docs/rules/factory-pattern.md new file mode 100644 index 000000000..c6699910d --- /dev/null +++ b/packages/eslint-plugin-sui/docs/rules/factory-pattern.md @@ -0,0 +1,35 @@ +# Ensure that our classes are using the convetion of has a static create method as factory. (`factory-pattern`) + +Please describe the origin of the rule here. + +## Rule Details + +This rule aims to... + +Examples of **incorrect** code for this rule: + +```js + +// fill me in + +``` + +Examples of **correct** code for this rule: + +```js + +// fill me in + +``` + +### Options + +If there are any options, describe them here. Otherwise, delete this section. + +## When Not To Use It + +Give a short description of when it would be appropriate to turn off this rule. + +## Further Reading + +If there are other links that describe the issue this rule addresses, please include them here in a bulleted list. diff --git a/packages/eslint-plugin-sui/package.json b/packages/eslint-plugin-sui/package.json new file mode 100644 index 000000000..99d36c4a1 --- /dev/null +++ b/packages/eslint-plugin-sui/package.json @@ -0,0 +1,39 @@ +{ + "name": "eslint-plugin-sui", + "version": "0.0.0", + "description": "Set of sui lint rules", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin" + ], + "author": "Sui", + "main": "./src/index.js", + "exports": "./src/index.js", + "scripts": { + "lint": "npm-run-all \"lint:*\"", + "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"", + "lint:js": "eslint .", + "test": "mocha tests --recursive", + "update:eslint-docs": "eslint-doc-generator" + }, + "dependencies": { + "requireindex": "^1.2.0", + "string-dedent": "^3.0.1" + }, + "devDependencies": { + "eslint": "^8.19.0", + "eslint-doc-generator": "^1.0.0", + "eslint-plugin-eslint-plugin": "^5.0.0", + "eslint-plugin-node": "^11.1.0", + "mocha": "^10.0.0", + "npm-run-all": "^4.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + }, + "license": "ISC" +} diff --git a/packages/eslint-plugin-sui/src/index.js b/packages/eslint-plugin-sui/src/index.js new file mode 100644 index 000000000..84d25af60 --- /dev/null +++ b/packages/eslint-plugin-sui/src/index.js @@ -0,0 +1,14 @@ +const FactoryPattern = require('./rules/factory-pattern.js') +const SerializeDeserialize = require('./rules/serialize-deserialize.js') + +// ------------------------------------------------------------------------------ +// Plugin Definition +// ------------------------------------------------------------------------------ + +// import all rules in lib/rules +module.exports = { + rules: { + 'factory-pattern': FactoryPattern, + 'serialize-deserialize': SerializeDeserialize + } +} diff --git a/packages/eslint-plugin-sui/src/rules/factory-pattern.js b/packages/eslint-plugin-sui/src/rules/factory-pattern.js new file mode 100644 index 000000000..779da9890 --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/factory-pattern.js @@ -0,0 +1,65 @@ +/** + * @fileoverview Ensure that our classes are using the convetion of has a static create method as factory. + * @author factory pattern + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'ensure to define at least one factory function', + recommended: true, + url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements/02-project-structure.md' + }, + fixable: null, + schema: [], + messages: { + notFoundFactoryFunction: dedent` + You have to define at least one static function that return an instance of your class. + Avoid to use the 'new' keyword directly in your code. + Use always a factory function + ` + } + }, + create: function (context) { + // variables should be defined here + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + // any helper functions should go here or else delete this section + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + ClassDeclaration(node) { + const hasStaticFactoryMethod = Boolean( + node.body?.body?.find(methodDefinition => { + return ( + methodDefinition.static && + methodDefinition.value?.body?.body?.find(body => body.type === 'ReturnStatement')?.argument?.callee.name === node.id.name // eslint-disable-line + ) + }) + ) + + if (!hasStaticFactoryMethod) { + return context.report({ + node, + messageId: 'notFoundFactoryFunction' + }) + } + } + } + } +} diff --git a/packages/eslint-plugin-sui/src/rules/forbidden-require.js b/packages/eslint-plugin-sui/src/rules/forbidden-require.js new file mode 100644 index 000000000..ec3dbe74e --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/forbidden-require.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Ensure that our coda doesnt have require (CJS) styles + * @author factory pattern + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'ensure to use only ESM (import) style', + recommended: true, + url: null + }, + fixable: null, + schema: [], + messages: { + badFileName: dedent``, + badClassName: dedent`` + } + }, + create: function (context) { + // variables should be defined here + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + // any helper functions should go here or else delete this section + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + ClassDeclaration(node) {} + } + } +} diff --git a/packages/eslint-plugin-sui/src/rules/naming-convention.js b/packages/eslint-plugin-sui/src/rules/naming-convention.js new file mode 100644 index 000000000..68477031f --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/naming-convention.js @@ -0,0 +1,46 @@ +/** + * @fileoverview Ensure that our classes are using the naming convention for UseCases, Services and Repositories + * @author factory pattern + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'ensure to use a proper naming convention', + recommended: true, + url: null + }, + fixable: null, + schema: [], + messages: { + badFileName: dedent``, + badClassName: dedent`` + } + }, + create: function (context) { + // variables should be defined here + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + + // any helper functions should go here or else delete this section + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return { + ClassDeclaration(node) {} + } + } +} diff --git a/packages/eslint-plugin-sui/src/rules/serialize-deserialize.js b/packages/eslint-plugin-sui/src/rules/serialize-deserialize.js new file mode 100644 index 000000000..fc3b6c7f5 --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/serialize-deserialize.js @@ -0,0 +1,93 @@ +/** + * @fileoverview ensure entity create - toJSON + * @creator david.lacedonia@adevinta.com + */ +'use strict' +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'ensure entity create - toJSON', + recommended: false, + url: null + }, + fixable: null, + schema: [], + messages: { + toJSONProperties: 'Missing toJSON properties ({{props}})', + invalidTOJSONProperties: 'toJSON should return an object', + missingToJSONMethod: dedent` + If your class has a 'static create' method. You have to define a 'toJSON' method too. + The output of the 'toJSON' should be the same as the input of your 'static create' method + `, + missingCreateMethod: dedent` + If your class has a 'toJSON' method. You have to define a 'static create' method too. + The output of the 'toJSON' should be the same as the input of your 'static create' method + ` + } + }, + + create(context) { + return { + ClassDeclaration(node) { + const create = node.body.body.find(i => i.key.name === 'create') + const toJSON = node.body.body.find(i => i.key.name === 'toJSON') + const className = node.id.name + + if (['UseCase', 'Service', 'Repository'].some(allowWord => className.includes(allowWord))) return // eslint-disable-line + + if (!create && !toJSON) return + + if (create && !toJSON) + return context.report({ + node: create, + messageId: 'missingToJSONMethod' + }) + + if (toJSON && !create) + return context.report({ + node: toJSON, + messageId: 'missingCreateMethod' + }) + + let createParams = create.value.params[0] || {properties: []} + if (createParams.left) { + createParams = createParams.left + } + + const createProperties = createParams.properties + const toJSONProperties = toJSON.value.body.body[0].argument.properties + + if (!toJSONProperties) { + return context.report({ + node: toJSON, + messageId: 'invalidTOJSONProperties' + }) + } + + const createProps = createProperties.map(i => i.key.name) + const toJSONProps = toJSONProperties.map(i => i.key.name) + + const missingToJSONProps = createProps.filter( + p => !toJSONProps.find(e => e === p) + ) + if (missingToJSONProps.length) { + context.report({ + node: toJSON, + messageId: 'toJSONProperties', + data: { + props: missingToJSONProps.join(', ') + } + }) + } + } + } + } +} diff --git a/packages/eslint-plugin-sui/tests/src/rules/factory-pattern.js b/packages/eslint-plugin-sui/tests/src/rules/factory-pattern.js new file mode 100644 index 000000000..e04d54faa --- /dev/null +++ b/packages/eslint-plugin-sui/tests/src/rules/factory-pattern.js @@ -0,0 +1,30 @@ +/** + * @fileoverview Ensure that our classes are using the convetion of has a static create method as factory. + * @author factory pattern + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ +import {RuleTester} from 'eslint' + +import rule from '../../../src/rules/factory-pattern' + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('factory-pattern', rule, { + valid: [ + // give me some code that won't trigger a warning + ], + + invalid: [ + { + code: "class Model { constructor() { this.name = 'John Doe' } }", + errors: [{message: 'Fill me in.', type: 'Me too'}] + } + ] +})