Skip to content

Commit

Permalink
feat(packages/eslint-plugin-sui): Make deprecated-decorator rules run…
Browse files Browse the repository at this point in the history
…ning on Classes, Methods and Ar
  • Loading branch information
oriolpuig committed Jul 24, 2024
1 parent 5b9ee47 commit ec08bf4
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -93,24 +34,24 @@ 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
const deprecatedDecoratorNode =
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
context.report({
node: nodeToRemark,
messageId: 'remarkWarningMessage',
data: {
methodName: nodeName
methodName: elementMessageName
}
})
}
Expand Down
138 changes: 71 additions & 67 deletions packages/eslint-plugin-sui/src/rules/decorator-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
'use strict'

const dedent = require('string-dedent')
const {getDecoratorsByNode, getElementName, getElementMessageName} = require('../utils/decorators')

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -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
}
}
}
76 changes: 76 additions & 0 deletions packages/eslint-plugin-sui/src/utils/decorators.js
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ec08bf4

Please sign in to comment.