Skip to content

Commit eafdfc3

Browse files
GTFalcaojs07
andauthored
Adding ESLint rule for action component MCP annotations (#15)
* Adding ESLint rule for action component MCP annotations * Update index.js Co-authored-by: js07 <[email protected]> * Optimizing code with astIncludesProperty function --------- Co-authored-by: js07 <[email protected]>
1 parent 26bd0eb commit eafdfc3

File tree

4 files changed

+128
-1
lines changed

4 files changed

+128
-1
lines changed

index.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,52 @@ function componentVersionTsMacroCheck(context, node) {
211211
}
212212
}
213213

214+
function componentActionAnnotationsCheck(context, node) {
215+
const component = getComponentFromNode(node);
216+
217+
if (!component) return;
218+
const { properties } = component;
219+
220+
const typeProp = findPropertyWithName("type", properties);
221+
if (typeProp?.value?.value !== "action") return;
222+
223+
const annotationsProp = findPropertyWithName("annotations", properties);
224+
225+
// Error 1 - annotations missing entirely
226+
if (!annotationsProp) {
227+
context.report({
228+
node: component,
229+
message: "Action component is missing required 'annotations' object",
230+
});
231+
return;
232+
}
233+
234+
// Error 2 - annotations is not an object expression
235+
if (annotationsProp.value.type !== "ObjectExpression") {
236+
context.report({
237+
node: annotationsProp.value,
238+
message: "Property 'annotations' must be an object expression",
239+
});
240+
return;
241+
}
242+
243+
// Error 3 - required keys missing
244+
const requiredKeys = [
245+
"destructiveHint",
246+
"openWorldHint",
247+
"readOnlyHint",
248+
];
249+
250+
for (const requiredKey of requiredKeys) {
251+
if (!astIncludesProperty(requiredKey, annotationsProp.value.properties)) {
252+
context.report({
253+
node: annotationsProp.value,
254+
message: `Property 'annotations' is missing required key: '${requiredKey}'`,
255+
});
256+
}
257+
}
258+
}
259+
214260
// Rules run on two different AST node types: ExpressionStatement (CJS) and
215261
// ExportDefaultDeclaration (ESM)
216262
module.exports = {
@@ -347,5 +393,17 @@ module.exports = {
347393
};
348394
},
349395
},
396+
"action-annotations": {
397+
create: function (context) {
398+
return {
399+
ExpressionStatement(node) {
400+
componentActionAnnotationsCheck(context, node);
401+
},
402+
ExportDefaultDeclaration(node) {
403+
componentActionAnnotationsCheck(context, node);
404+
},
405+
};
406+
},
407+
},
350408
},
351409
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/eslint-plugin-pipedream",
3-
"version": "0.2.5",
3+
"version": "0.3.0",
44
"description": "ESLint plugin for Pipedream components: https://pipedream.com/docs/components/api/",
55
"main": "index.js",
66
"scripts": {

tests/components.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,42 @@ module.exports = {
174174
type: "action",
175175
version: "0.0.{{ts}}",
176176
},
177+
validActionWithAnnotations: {
178+
key: "test",
179+
name: "Test",
180+
description: "foo",
181+
type: "action",
182+
version: "0.0.1",
183+
annotations: {
184+
destructiveHint: false,
185+
openWorldHint: false,
186+
readOnlyHint: true,
187+
},
188+
},
189+
actionMissingAnnotations: {
190+
key: "test",
191+
name: "Test",
192+
description: "foo",
193+
type: "action",
194+
version: "0.0.1",
195+
},
196+
actionMissingAnnotationKey: {
197+
key: "test",
198+
name: "Test",
199+
description: "foo",
200+
type: "action",
201+
version: "0.0.1",
202+
annotations: {
203+
destructiveHint: false,
204+
openWorldHint: false,
205+
},
206+
},
207+
actionInvalidAnnotations: {
208+
key: "test",
209+
name: "Test",
210+
description: "foo",
211+
type: "action",
212+
version: "0.0.1",
213+
annotations: "invalid",
214+
},
177215
};

tests/rules.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const {
1919
badSourceName,
2020
badSourceDescription,
2121
tsVersion,
22+
validActionWithAnnotations,
23+
actionMissingAnnotations,
24+
actionMissingAnnotationKey,
25+
actionInvalidAnnotations,
2226
} = require("./components");
2327

2428
const ruleTester = new RuleTester({
@@ -129,6 +133,33 @@ const componentTestConfigs = [
129133
invalidComponent: tsVersion,
130134
errorMessage: "{{ts}} macro should be removed before committing",
131135
},
136+
{
137+
name: "action-annotations-missing",
138+
ruleName: "action-annotations",
139+
validComponents: [
140+
validActionWithAnnotations,
141+
],
142+
invalidComponent: actionMissingAnnotations,
143+
errorMessage: "Action component is missing required 'annotations' object",
144+
},
145+
{
146+
name: "action-annotations-missing-key",
147+
ruleName: "action-annotations",
148+
validComponents: [
149+
validActionWithAnnotations,
150+
],
151+
invalidComponent: actionMissingAnnotationKey,
152+
errorMessage: "Property 'annotations' is missing required key: 'readOnlyHint'",
153+
},
154+
{
155+
name: "action-annotations-invalid",
156+
ruleName: "action-annotations",
157+
validComponents: [
158+
validActionWithAnnotations,
159+
],
160+
invalidComponent: actionInvalidAnnotations,
161+
errorMessage: "Property 'annotations' must be an object expression",
162+
},
132163
];
133164

134165
const componentTestCases = componentTestConfigs.map(makeComponentTestCase);

0 commit comments

Comments
 (0)