104 lines
4.6 KiB
JavaScript
104 lines
4.6 KiB
JavaScript
// @ts-check
|
|
// *****************************************************************************
|
|
// Copyright (C) 2024 EclipseSource and others.
|
|
//
|
|
// This program and the accompanying materials are made available under the
|
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
// http://www.eclipse.org/legal/epl-2.0.
|
|
//
|
|
// This Source Code may also be made available under the following Secondary
|
|
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
// with the GNU Classpath Exception which is available at
|
|
// https://www.gnu.org/software/classpath/license.html.
|
|
//
|
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
// *****************************************************************************
|
|
|
|
/**
|
|
* @typedef {import('@typescript-eslint/utils').TSESTree.ClassDeclaration} ClassDeclaration
|
|
* @typedef {import('@typescript-eslint/utils').TSESTree.ClassElement} ClassElement
|
|
* @typedef {import('@typescript-eslint/utils').TSESTree.Decorator} Decorator
|
|
* @typedef {import('@typescript-eslint/utils').TSESTree.MethodDefinition} MethodDefinition
|
|
* @typedef {import('@typescript-eslint/utils').TSESTree.Parameter} Parameter
|
|
* @typedef {import('estree').Node} Node
|
|
* @typedef {import('eslint').Rule.RuleModule} RuleModule
|
|
*/
|
|
|
|
/**
|
|
* Type guard to check if a ClassElement is a MethodDefinition.
|
|
* @param {ClassElement} element
|
|
* @returns {element is MethodDefinition}
|
|
*/
|
|
function isMethodDefinition(element) {
|
|
return element.type === 'MethodDefinition';
|
|
}
|
|
|
|
/** @type {RuleModule} */
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description:
|
|
'Ensure @injectable classes have annotated constructor parameters',
|
|
},
|
|
messages: {
|
|
missingAnnotation: 'Constructor parameters in an @injectable class must be annotated with @inject, @unmanaged or @multiInject',
|
|
},
|
|
},
|
|
create(context) {
|
|
return {
|
|
/**
|
|
* @param {ClassDeclaration} node
|
|
*/
|
|
ClassDeclaration(node) {
|
|
// Check if the class has a decorator named `injectable`
|
|
const hasInjectableDecorator = node.decorators?.some(
|
|
(/** @type {Decorator} */ decorator) =>
|
|
decorator.expression.type === 'CallExpression' &&
|
|
decorator.expression.callee.type === 'Identifier' &&
|
|
decorator.expression.callee.name === 'injectable'
|
|
);
|
|
|
|
if (hasInjectableDecorator) {
|
|
// Find the constructor method within the class body
|
|
const constructor = node.body.body.find(
|
|
member =>
|
|
isMethodDefinition(member) &&
|
|
member.kind === 'constructor'
|
|
);
|
|
|
|
if (
|
|
constructor &&
|
|
// We need to re-apply 'isMethodDefinition' here because the type guard is not properly preserved
|
|
isMethodDefinition(constructor) &&
|
|
constructor.value &&
|
|
constructor.value.params.length > 0
|
|
) {
|
|
constructor.value.params.forEach(
|
|
/** @type {Parameter} */ param => {
|
|
// Check if each constructor parameter has a decorator
|
|
const hasAnnotation = param.decorators?.some(
|
|
(/** @type {Decorator} */ decorator) =>
|
|
decorator.expression.type === 'CallExpression' &&
|
|
decorator.expression.callee.type === 'Identifier' &&
|
|
(decorator.expression.callee.name === 'inject' ||
|
|
decorator.expression.callee.name === 'unmanaged' ||
|
|
decorator.expression.callee.name === 'multiInject')
|
|
);
|
|
|
|
if (!hasAnnotation) {
|
|
context.report({
|
|
node: /** @type Node */ (param),
|
|
messageId: 'missingAnnotation',
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|