Files
theia-code-os/dev-packages/private-eslint-plugin/rules/annotation-check.js
mawkone 8bb5110148
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled
deploy: current vibn theia state
Made-with: Cursor
2026-02-27 12:01:08 -08:00

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',
});
}
}
);
}
}
},
};
},
};