From 9ebc5aff5688330918be1db48a98b745031f3514 Mon Sep 17 00:00:00 2001 From: Oriol Puig Date: Wed, 3 Jul 2024 19:03:12 +0200 Subject: [PATCH 1/4] feat(packages/sui-decorators): Create new @Deprecated() decorator --- .../src/decorators/deprecated/index.js | 46 ++++++++++++++++++ packages/sui-decorators/src/index.js | 3 +- .../test/browser/DeprecatedSpec.js | 48 +++++++++++++++++++ .../test/server/DeprecatedSpec.js | 48 +++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 packages/sui-decorators/src/decorators/deprecated/index.js create mode 100644 packages/sui-decorators/test/browser/DeprecatedSpec.js create mode 100644 packages/sui-decorators/test/server/DeprecatedSpec.js diff --git a/packages/sui-decorators/src/decorators/deprecated/index.js b/packages/sui-decorators/src/decorators/deprecated/index.js new file mode 100644 index 000000000..4b7c3e9e8 --- /dev/null +++ b/packages/sui-decorators/src/decorators/deprecated/index.js @@ -0,0 +1,46 @@ +import isNode from '../../helpers/isNode.js' + +const noop = () => {} + +const getListener = () => { + const listener = isNode ? global.__SUI_DECORATOR_DEPRECATED_REPORTER__ : window.__SUI_DECORATOR_DEPRECATED_REPORTER__ + return listener || noop +} + +const _runner = ({instance, original, config} = {}) => { + return function (...args) { + const listener = getListener() + listener(config) + + const {message} = config + + if (process.env.NODE_ENV !== 'production') { + console.warn(message) + } + + return original.apply(instance, args) + } +} + +export function Deprecated(config = {message: ''}) { + return function (target, fnName, descriptor) { + const {value: fn, configurable, enumerable} = descriptor + + return Object.assign( + {}, + { + configurable, + enumerable, + value(...args) { + const _fnRunner = _runner({ + instance: this, + original: fn, + config + }) + + return _fnRunner.apply(this, args) + } + } + ) + } +} diff --git a/packages/sui-decorators/src/index.js b/packages/sui-decorators/src/index.js index e55284768..94932302f 100644 --- a/packages/sui-decorators/src/index.js +++ b/packages/sui-decorators/src/index.js @@ -1,6 +1,7 @@ import {cache, invalidateCache} from './decorators/cache/index.js' +import {Deprecated} from './decorators/deprecated/index.js' import inlineError from './decorators/error.js' import streamify from './decorators/streamify.js' import tracer from './decorators/tracer/index.js' -export {cache, invalidateCache, streamify, inlineError, tracer} +export {cache, Deprecated, invalidateCache, streamify, inlineError, tracer} diff --git a/packages/sui-decorators/test/browser/DeprecatedSpec.js b/packages/sui-decorators/test/browser/DeprecatedSpec.js new file mode 100644 index 000000000..57503b138 --- /dev/null +++ b/packages/sui-decorators/test/browser/DeprecatedSpec.js @@ -0,0 +1,48 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {Deprecated} from '../../src/decorators/deprecated/index.js' + +describe('Deprecated decorator', () => { + beforeEach(() => { + sinon.stub(console, 'warn') + window.__SUI_DECORATOR_DEPRECATED_REPORTER__ = undefined + }) + + afterEach(() => { + sinon.restore() + }) + + it('should exist', () => { + expect(Deprecated).to.exist + expect(Deprecated).to.be.a('function') + }) + + it('should write a console.warn into console', async () => { + class Buzz { + @Deprecated({key: 'returnASuccessPromise', message: 'This method is deprecated'}) + returnASuccessPromise() { + return Promise.resolve(true) + } + } + + const buzz = new Buzz() + expect(await buzz.returnASuccessPromise()).to.be.eql(true) + expect(console.warn.calledOnce).to.be.true + }) + + it('should call to listener', async () => { + window.__SUI_DECORATOR_DEPRECATED_REPORTER__ = sinon.spy() + + class Buzz { + @Deprecated({key: 'returnASuccessPromise', message: 'This method is deprecated'}) + returnASuccessPromise() { + return Promise.resolve(true) + } + } + + const buzz = new Buzz() + expect(await buzz.returnASuccessPromise()).to.be.eql(true) + expect(window.__SUI_DECORATOR_DEPRECATED_REPORTER__.calledOnce).to.be.true + }) +}) diff --git a/packages/sui-decorators/test/server/DeprecatedSpec.js b/packages/sui-decorators/test/server/DeprecatedSpec.js new file mode 100644 index 000000000..66cccd869 --- /dev/null +++ b/packages/sui-decorators/test/server/DeprecatedSpec.js @@ -0,0 +1,48 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {Deprecated} from '../../src/decorators/deprecated/index.js' + +describe('Deprecated decorator', () => { + beforeEach(() => { + sinon.stub(console, 'warn') + global.__SUI_DECORATOR_DEPRECATED_REPORTER__ = undefined + }) + + afterEach(() => { + sinon.restore() + }) + + it('should exist', () => { + expect(Deprecated).to.exist + expect(Deprecated).to.be.a('function') + }) + + it('should write a console.warn into console', async () => { + class Buzz { + @Deprecated({key: 'returnASuccessPromise', message: 'This method is deprecated'}) + returnASuccessPromise() { + return Promise.resolve(true) + } + } + + const buzz = new Buzz() + expect(await buzz.returnASuccessPromise()).to.be.eql(true) + expect(console.warn.calledOnce).to.be.true + }) + + it('should call to listener to enable monitoring', async () => { + global.__SUI_DECORATOR_DEPRECATED_REPORTER__ = sinon.spy() + + class Buzz { + @Deprecated({key: 'returnASuccessPromise', message: 'This method is deprecated'}) + returnASuccessPromise() { + return Promise.resolve(true) + } + } + + const buzz = new Buzz() + expect(await buzz.returnASuccessPromise()).to.be.eql(true) + expect(global.__SUI_DECORATOR_DEPRECATED_REPORTER__.calledOnce).to.be.true + }) +}) From aa22a8dfec796998c4f3d52c7154a3dd643c458a Mon Sep 17 00:00:00 2001 From: Oriol Puig Date: Fri, 19 Jul 2024 14:34:00 +0200 Subject: [PATCH 2/4] feat(packages/eslint-plugin-sui): Create rules for @Deprecated() decorator --- packages/eslint-plugin-sui/src/index.js | 6 +- .../decorator-deprecated-remark-method.js | 51 ++++++++ .../src/rules/decorator-deprecated.js | 115 ++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js create mode 100644 packages/eslint-plugin-sui/src/rules/decorator-deprecated.js diff --git a/packages/eslint-plugin-sui/src/index.js b/packages/eslint-plugin-sui/src/index.js index 1620724c4..a4bbe561a 100644 --- a/packages/eslint-plugin-sui/src/index.js +++ b/packages/eslint-plugin-sui/src/index.js @@ -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 DecoratorDeprecated = require('./rules/decorator-deprecated.js') +const DecoratorDeprecatedRemarkMethod = require('./rules/decorator-deprecated-remark-method.js') const LayersArch = require('./rules/layers-architecture.js') // ------------------------------------------------------------------------------ @@ -15,6 +17,8 @@ module.exports = { 'serialize-deserialize': SerializeDeserialize, commonjs: CommonJS, decorators: Decorators, - 'layers-arch': LayersArch + 'layers-arch': LayersArch, + 'decorator-deprecated': DecoratorDeprecated, + 'decorator-deprecated-remark-method': DecoratorDeprecatedRemarkMethod } } diff --git a/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js new file mode 100644 index 000000000..5c47a7e90 --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js @@ -0,0 +1,51 @@ +/** + * @fileoverview Ensure that method using @Deprecated() displays a warning alert + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Ensure that method using @Deprecated() displays a warning alert', + recommended: true, + url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements' + }, + fixable: 'code', + schema: [], + messages: {} + }, + create: function (context) { + // TODO: Check using decorator in a Class. + + return { + MethodDefinition(node) { + // Method + const method = node + + // Method decorators + const methodDecorators = method.decorators + const hasDecorators = methodDecorators?.length > 0 + + if (!hasDecorators) return + + // Get the @Deprecated() decorator from method + const deprecatedDecoratorNode = + hasDecorators && methodDecorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') + + if (!deprecatedDecoratorNode) return + + // RULE: Mark method with a warning + context.report({ + node: method.key, + message: 'This method is marked as a deprecated.' + }) + } + } + } +} diff --git a/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js b/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js new file mode 100644 index 000000000..11465ddd6 --- /dev/null +++ b/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js @@ -0,0 +1,115 @@ +/** + * @fileoverview Ensure that @Deprecated() decorator is used as expected + */ +'use strict' + +const dedent = require('string-dedent') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Ensure that @Deprecated() decorator is used as expected', + recommended: true, + url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements' + }, + fixable: 'code', + schema: [], + messages: { + notFoundDecoratorArgumentError: dedent` + The @Deprecated() decorator must have arguments. + `, + notFoundKeyDecoratorArgumentError: dedent` + The @Deprecated() decorator must have a key property. + `, + notFoundMessageDecoratorArgumentError: dedent` + The @Deprecated() decorator must have a message property. + ` + } + }, + create: function (context) { + // TODO: Check using decorator in a Class. + return { + MethodDefinition(node) { + // Method + const method = node + const methodName = method.key?.name + + // Method decorators + const methodDecorators = method.decorators + const hasDecorators = methodDecorators?.length > 0 + + if (!hasDecorators) return + + // Get the @Deprecated() decorator from method + const deprecatedDecoratorNode = + hasDecorators && methodDecorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') + + if (!deprecatedDecoratorNode) return + + const methodArguments = deprecatedDecoratorNode?.expression?.arguments + const hasArgument = methodArguments.length === 1 + const argumentDecorator = hasArgument && methodArguments[0] + const isObjectExpression = hasArgument && argumentDecorator.type === 'ObjectExpression' + const argumentsAreInvalid = !hasArgument || !isObjectExpression + + // Get decorator arguments: key and message + const keyProperty = + !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'key') + const messageProperty = + !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'message') + + // RULE: Decorator must have 1 argument as an object with Key and Message properties + if (argumentsAreInvalid || (!keyProperty && !messageProperty)) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${methodName}', message: 'The ${methodName} function is deprecated.'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) + return + } + + // RULE: Decorator must have a key property and generates it if it doesn't exist + if (!keyProperty && messageProperty) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundKeyDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${methodName}', message: '${messageProperty.value.value}'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) + } + + // RULE: Decorator must have a message property and generates it if it doesn't exist + if (keyProperty && !messageProperty) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundMessageDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${keyProperty.value.value}', message: 'The ${methodName} function is deprecated.'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) + } + } + } + } +} From 5b9ee47f596cd516d6204adeac0e03a8bf9cb8b1 Mon Sep 17 00:00:00 2001 From: Oriol Puig Date: Wed, 24 Jul 2024 14:40:16 +0200 Subject: [PATCH 3/4] feat(packages/eslint-plugin-sui): Make decorator-deprecated-remark-method runs on classes --- .../decorator-deprecated-remark-method.js | 113 ++++++++++++++---- packages/sui-lint/eslintrc.js | 4 +- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js index 5c47a7e90..1cbe14101 100644 --- a/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js +++ b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js @@ -3,10 +3,72 @@ */ 'use strict' +const dedent = require('string-dedent') + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ +function getElementName(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + const className = node.id?.name ?? 'UnknownClass' + return `class ${className}` + } + + if (isArrowFunction) { + const methodNode = node.parent + const classNode = methodNode?.parent?.parent + const className = classNode.id?.name ?? 'UnknownClass' + const methodName = methodNode.key?.name ?? 'UnknownMethod' + + return `method ${className}.${methodName}` + } + + if (isAMethod) { + const classNode = node.parent?.parent + const className = classNode.id?.name ?? 'UnknownClass' + const methodName = node.key?.name ?? 'UnknownMethod' + + return `method ${className}.${methodName}` + } + + return 'unknown' +} + +function getDecoratorsNode(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + return node.decorators + } + + if (isArrowFunction) { + const methodNode = node.parent + return methodNode.decorators ?? [] + } + + if (isAMethod) { + return node.decorators ?? [] + } + + return [] +} + +function remarkElement(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + return node.id + } + + if (isArrowFunction) { + const methodNode = node.parent + return methodNode.key + } + + if (isAMethod) { + return node.key + } + + return node +} + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -18,34 +80,45 @@ module.exports = { }, fixable: 'code', schema: [], - messages: {} + messages: { + remarkWarningMessage: dedent` + The {{methodName}} is marked as a deprecated. + ` + } }, create: function (context) { - // TODO: Check using decorator in a Class. + function highlightNode(node) { + const isAClass = node.type === 'ClassDeclaration' + const isArrowFunction = node.type === 'ArrowFunctionExpression' + const isAMethod = node.type === 'MethodDefinition' - return { - MethodDefinition(node) { - // Method - const method = node + const nodeName = getElementName(node, {isAClass, isAMethod, isArrowFunction}) + const decorators = getDecoratorsNode(node, {isAClass, isAMethod, isArrowFunction}) + const hasDecorators = decorators?.length > 0 - // Method decorators - const methodDecorators = method.decorators - const hasDecorators = methodDecorators?.length > 0 + // Get the @Deprecated() decorator from node decorators + const deprecatedDecoratorNode = + hasDecorators && decorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') - if (!hasDecorators) return + if (!deprecatedDecoratorNode) return + console.log(nodeName, deprecatedDecoratorNode) - // Get the @Deprecated() decorator from method - const deprecatedDecoratorNode = - hasDecorators && methodDecorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') + const nodeToRemark = remarkElement(node, {isAClass, isAMethod, isArrowFunction}) - if (!deprecatedDecoratorNode) return + // RULE: Mark method with a warning + context.report({ + node: nodeToRemark, + messageId: 'remarkWarningMessage', + data: { + methodName: nodeName + } + }) + } - // RULE: Mark method with a warning - context.report({ - node: method.key, - message: 'This method is marked as a deprecated.' - }) - } + return { + ClassDeclaration: highlightNode, + ArrowFunctionExpression: highlightNode, + MethodDefinition: highlightNode } } } diff --git a/packages/sui-lint/eslintrc.js b/packages/sui-lint/eslintrc.js index 06787f9b6..6e7cc3c9a 100644 --- a/packages/sui-lint/eslintrc.js +++ b/packages/sui-lint/eslintrc.js @@ -239,7 +239,9 @@ module.exports = { rules: { 'sui/factory-pattern': RULES.WARNING, 'sui/serialize-deserialize': RULES.WARNING, - 'sui/decorators': RULES.WARNING + 'sui/decorators': RULES.WARNING, + 'sui/decorator-deprecated': RULES.ERROR, + 'sui/decorator-deprecated-remark-method': RULES.WARNING } }, { From ec08bf4bd5af6f4466f0d2291d0e8f4efcb6971a Mon Sep 17 00:00:00 2001 From: Oriol Puig Date: Wed, 24 Jul 2024 15:17:27 +0200 Subject: [PATCH 4/4] feat(packages/eslint-plugin-sui): Make deprecated-decorator rules running on Classes, Methods and Ar --- .../decorator-deprecated-remark-method.js | 67 +-------- .../src/rules/decorator-deprecated.js | 138 +++++++++--------- .../eslint-plugin-sui/src/utils/decorators.js | 76 ++++++++++ 3 files changed, 151 insertions(+), 130 deletions(-) create mode 100644 packages/eslint-plugin-sui/src/utils/decorators.js diff --git a/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js index 1cbe14101..214af8a68 100644 --- a/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js +++ b/packages/eslint-plugin-sui/src/rules/decorator-deprecated-remark-method.js @@ -4,71 +4,12 @@ 'use strict' const dedent = require('string-dedent') +const {getDecoratorsByNode, getElementMessageName, getElementName, remarkElement} = require('../utils/decorators.js') // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ -function getElementName(node, {isAClass, isAMethod, isArrowFunction}) { - if (isAClass) { - const className = node.id?.name ?? 'UnknownClass' - return `class ${className}` - } - - if (isArrowFunction) { - const methodNode = node.parent - const classNode = methodNode?.parent?.parent - const className = classNode.id?.name ?? 'UnknownClass' - const methodName = methodNode.key?.name ?? 'UnknownMethod' - - return `method ${className}.${methodName}` - } - - if (isAMethod) { - const classNode = node.parent?.parent - const className = classNode.id?.name ?? 'UnknownClass' - const methodName = node.key?.name ?? 'UnknownMethod' - - return `method ${className}.${methodName}` - } - - return 'unknown' -} - -function getDecoratorsNode(node, {isAClass, isAMethod, isArrowFunction}) { - if (isAClass) { - return node.decorators - } - - if (isArrowFunction) { - const methodNode = node.parent - return methodNode.decorators ?? [] - } - - if (isAMethod) { - return node.decorators ?? [] - } - - return [] -} - -function remarkElement(node, {isAClass, isAMethod, isArrowFunction}) { - if (isAClass) { - return node.id - } - - if (isArrowFunction) { - const methodNode = node.parent - return methodNode.key - } - - if (isAMethod) { - return node.key - } - - return node -} - /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -93,7 +34,7 @@ module.exports = { const isAMethod = node.type === 'MethodDefinition' const nodeName = getElementName(node, {isAClass, isAMethod, isArrowFunction}) - const decorators = getDecoratorsNode(node, {isAClass, isAMethod, isArrowFunction}) + const decorators = getDecoratorsByNode(node, {isAClass, isAMethod, isArrowFunction}) const hasDecorators = decorators?.length > 0 // Get the @Deprecated() decorator from node decorators @@ -101,8 +42,8 @@ module.exports = { hasDecorators && decorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') if (!deprecatedDecoratorNode) return - console.log(nodeName, deprecatedDecoratorNode) + const elementMessageName = getElementMessageName(nodeName, {isAClass, isAMethod, isArrowFunction}) const nodeToRemark = remarkElement(node, {isAClass, isAMethod, isArrowFunction}) // RULE: Mark method with a warning @@ -110,7 +51,7 @@ module.exports = { node: nodeToRemark, messageId: 'remarkWarningMessage', data: { - methodName: nodeName + methodName: elementMessageName } }) } diff --git a/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js b/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js index 11465ddd6..7d9f4b019 100644 --- a/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js +++ b/packages/eslint-plugin-sui/src/rules/decorator-deprecated.js @@ -4,6 +4,7 @@ 'use strict' const dedent = require('string-dedent') +const {getDecoratorsByNode, getElementName, getElementMessageName} = require('../utils/decorators') // ------------------------------------------------------------------------------ // Rule Definition @@ -33,83 +34,86 @@ module.exports = { } }, create: function (context) { - // TODO: Check using decorator in a Class. - return { - MethodDefinition(node) { - // Method - const method = node - const methodName = method.key?.name + function ruleRunner(node) { + const isAClass = node.type === 'ClassDeclaration' + const isArrowFunction = node.type === 'ArrowFunctionExpression' + const isAMethod = node.type === 'MethodDefinition' - // Method decorators - const methodDecorators = method.decorators - const hasDecorators = methodDecorators?.length > 0 + const nodeName = getElementName(node, {isAClass, isAMethod, isArrowFunction}) + const decorators = getDecoratorsByNode(node, {isAClass, isAMethod, isArrowFunction}) + const hasDecorators = decorators?.length > 0 - if (!hasDecorators) return + // Get the @Deprecated() decorator from node decorators + const deprecatedDecoratorNode = + hasDecorators && decorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') - // Get the @Deprecated() decorator from method - const deprecatedDecoratorNode = - hasDecorators && methodDecorators?.find(decorator => decorator?.expression?.callee?.name === 'Deprecated') + if (!deprecatedDecoratorNode) return - if (!deprecatedDecoratorNode) return + const deprecatedDecoratorArguments = deprecatedDecoratorNode.expression?.arguments + // The decorator must have 1 argument and it should be an object + const hasArgument = deprecatedDecoratorArguments.length === 1 + const argumentDecorator = hasArgument && deprecatedDecoratorArguments[0] + const isObjectExpression = hasArgument && argumentDecorator.type === 'ObjectExpression' + const argumentsAreInvalid = !hasArgument || !isObjectExpression - const methodArguments = deprecatedDecoratorNode?.expression?.arguments - const hasArgument = methodArguments.length === 1 - const argumentDecorator = hasArgument && methodArguments[0] - const isObjectExpression = hasArgument && argumentDecorator.type === 'ObjectExpression' - const argumentsAreInvalid = !hasArgument || !isObjectExpression + // Get decorator arguments: key and message + const keyProperty = !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'key') + const messageProperty = + !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'message') - // Get decorator arguments: key and message - const keyProperty = - !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'key') - const messageProperty = - !argumentsAreInvalid && argumentDecorator.properties?.find(prop => prop?.key?.name === 'message') + const elementMessageName = getElementMessageName(nodeName, {isAClass, isAMethod, isArrowFunction}) - // RULE: Decorator must have 1 argument as an object with Key and Message properties - if (argumentsAreInvalid || (!keyProperty && !messageProperty)) { - context.report({ - node: deprecatedDecoratorNode, - messageId: 'notFoundDecoratorArgumentError', - *fix(fixer) { - yield fixer.insertTextBefore( - deprecatedDecoratorNode, - `\n @Deprecated({key: '${methodName}', message: 'The ${methodName} function is deprecated.'})` - ) - yield fixer.remove(deprecatedDecoratorNode) - } - }) - return - } + // RULE: Decorator must have 1 argument as an object with Key and Message properties + if (argumentsAreInvalid || (!keyProperty && !messageProperty)) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${nodeName}', message: 'The ${elementMessageName} is deprecated.'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) + return + } - // RULE: Decorator must have a key property and generates it if it doesn't exist - if (!keyProperty && messageProperty) { - context.report({ - node: deprecatedDecoratorNode, - messageId: 'notFoundKeyDecoratorArgumentError', - *fix(fixer) { - yield fixer.insertTextBefore( - deprecatedDecoratorNode, - `\n @Deprecated({key: '${methodName}', message: '${messageProperty.value.value}'})` - ) - yield fixer.remove(deprecatedDecoratorNode) - } - }) - } + // RULE: Decorator must have a key property and generates it if it doesn't exist + if (!keyProperty && messageProperty) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundKeyDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${nodeName}', message: '${messageProperty.value.value}'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) + } - // RULE: Decorator must have a message property and generates it if it doesn't exist - if (keyProperty && !messageProperty) { - context.report({ - node: deprecatedDecoratorNode, - messageId: 'notFoundMessageDecoratorArgumentError', - *fix(fixer) { - yield fixer.insertTextBefore( - deprecatedDecoratorNode, - `\n @Deprecated({key: '${keyProperty.value.value}', message: 'The ${methodName} function is deprecated.'})` - ) - yield fixer.remove(deprecatedDecoratorNode) - } - }) - } + // RULE: Decorator must have a message property and generates it if it doesn't exist + if (keyProperty && !messageProperty) { + context.report({ + node: deprecatedDecoratorNode, + messageId: 'notFoundMessageDecoratorArgumentError', + *fix(fixer) { + yield fixer.insertTextBefore( + deprecatedDecoratorNode, + `\n @Deprecated({key: '${keyProperty.value.value}', message: 'The ${elementMessageName} function is deprecated.'})` + ) + yield fixer.remove(deprecatedDecoratorNode) + } + }) } } + + return { + ClassDeclaration: ruleRunner, + MethodDefinition: ruleRunner, + ArrowFunctionExpression: ruleRunner + } } } diff --git a/packages/eslint-plugin-sui/src/utils/decorators.js b/packages/eslint-plugin-sui/src/utils/decorators.js new file mode 100644 index 000000000..2ad0c83c6 --- /dev/null +++ b/packages/eslint-plugin-sui/src/utils/decorators.js @@ -0,0 +1,76 @@ +function getDecoratorsByNode(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + return node.decorators + } + + if (isArrowFunction) { + const methodNode = node.parent + return methodNode.decorators ?? [] + } + + if (isAMethod) { + return node.decorators ?? [] + } + + return [] +} + +function getElementName(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + const className = node.id?.name ?? 'UnknownClass' + return `${className}` + } + + if (isArrowFunction) { + const methodNode = node.parent + const classNode = methodNode?.parent?.parent + const className = classNode.id?.name ?? 'UnknownClass' + const methodName = methodNode.key?.name ?? 'UnknownMethod' + + return `${className}.${methodName}` + } + + if (isAMethod) { + const classNode = node.parent?.parent + const className = classNode.id?.name ?? 'UnknownClass' + const methodName = node.key?.name ?? 'UnknownMethod' + + return `${className}.${methodName}` + } + + return 'unknown' +} + +function getElementMessageName(elementName, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + return `class ${elementName}` + } + + if (isAMethod || isArrowFunction) { + return `method ${elementName}` + } + + return 'Unknown' +} + +function remarkElement(node, {isAClass, isAMethod, isArrowFunction}) { + if (isAClass) { + return node.id + } + + if (isArrowFunction) { + const methodNode = node.parent + return methodNode.key + } + + if (isAMethod) { + return node.key + } + + return node +} + +module.exports.getDecoratorsByNode = getDecoratorsByNode +module.exports.getElementMessageName = getElementMessageName +module.exports.getElementName = getElementName +module.exports.remarkElement = remarkElement