diff --git a/index.js b/index.js index ab8428a..86c685b 100644 --- a/index.js +++ b/index.js @@ -211,6 +211,52 @@ function componentVersionTsMacroCheck(context, node) { } } +function componentActionAnnotationsCheck(context, node) { + const component = getComponentFromNode(node); + + if (!component) return; + const { properties } = component; + + const typeProp = findPropertyWithName("type", properties); + if (typeProp?.value?.value !== "action") return; + + const annotationsProp = findPropertyWithName("annotations", properties); + + // Error 1 - annotations missing entirely + if (!annotationsProp) { + context.report({ + node: component, + message: "Action component is missing required 'annotations' object", + }); + return; + } + + // Error 2 - annotations is not an object expression + if (annotationsProp.value.type !== "ObjectExpression") { + context.report({ + node: annotationsProp.value, + message: "Property 'annotations' must be an object expression", + }); + return; + } + + // Error 3 - required keys missing + const requiredKeys = [ + "destructiveHint", + "openWorldHint", + "readOnlyHint", + ]; + + for (const requiredKey of requiredKeys) { + if (!astIncludesProperty(requiredKey, annotationsProp.value.properties)) { + context.report({ + node: annotationsProp.value, + message: `Property 'annotations' is missing required key: '${requiredKey}'`, + }); + } + } +} + // Rules run on two different AST node types: ExpressionStatement (CJS) and // ExportDefaultDeclaration (ESM) module.exports = { @@ -347,5 +393,17 @@ module.exports = { }; }, }, + "action-annotations": { + create: function (context) { + return { + ExpressionStatement(node) { + componentActionAnnotationsCheck(context, node); + }, + ExportDefaultDeclaration(node) { + componentActionAnnotationsCheck(context, node); + }, + }; + }, + }, }, }; diff --git a/package.json b/package.json index b1eef55..094063d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/eslint-plugin-pipedream", - "version": "0.2.5", + "version": "0.3.0", "description": "ESLint plugin for Pipedream components: https://pipedream.com/docs/components/api/", "main": "index.js", "scripts": { diff --git a/tests/components.js b/tests/components.js index 85870d5..b9b572f 100644 --- a/tests/components.js +++ b/tests/components.js @@ -174,4 +174,42 @@ module.exports = { type: "action", version: "0.0.{{ts}}", }, + validActionWithAnnotations: { + key: "test", + name: "Test", + description: "foo", + type: "action", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: false, + readOnlyHint: true, + }, + }, + actionMissingAnnotations: { + key: "test", + name: "Test", + description: "foo", + type: "action", + version: "0.0.1", + }, + actionMissingAnnotationKey: { + key: "test", + name: "Test", + description: "foo", + type: "action", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: false, + }, + }, + actionInvalidAnnotations: { + key: "test", + name: "Test", + description: "foo", + type: "action", + version: "0.0.1", + annotations: "invalid", + }, }; diff --git a/tests/rules.test.js b/tests/rules.test.js index fd3ed82..ab7d139 100644 --- a/tests/rules.test.js +++ b/tests/rules.test.js @@ -19,6 +19,10 @@ const { badSourceName, badSourceDescription, tsVersion, + validActionWithAnnotations, + actionMissingAnnotations, + actionMissingAnnotationKey, + actionInvalidAnnotations, } = require("./components"); const ruleTester = new RuleTester({ @@ -129,6 +133,33 @@ const componentTestConfigs = [ invalidComponent: tsVersion, errorMessage: "{{ts}} macro should be removed before committing", }, + { + name: "action-annotations-missing", + ruleName: "action-annotations", + validComponents: [ + validActionWithAnnotations, + ], + invalidComponent: actionMissingAnnotations, + errorMessage: "Action component is missing required 'annotations' object", + }, + { + name: "action-annotations-missing-key", + ruleName: "action-annotations", + validComponents: [ + validActionWithAnnotations, + ], + invalidComponent: actionMissingAnnotationKey, + errorMessage: "Property 'annotations' is missing required key: 'readOnlyHint'", + }, + { + name: "action-annotations-invalid", + ruleName: "action-annotations", + validComponents: [ + validActionWithAnnotations, + ], + invalidComponent: actionInvalidAnnotations, + errorMessage: "Property 'annotations' must be an object expression", + }, ]; const componentTestCases = componentTestConfigs.map(makeComponentTestCase);