Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilly-pianos-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/eslint-plugin": patch
---

An eslint plugin that ensures uniqueness on task keys
6 changes: 5 additions & 1 deletion examples/nextjs-example/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals"],
"plugins": ["@trigger.dev"],
"rules": {
"@trigger.dev/no-duplicated-task-keys": 2
}
}
1 change: 1 addition & 0 deletions examples/nextjs-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@trigger.dev/sdk": "workspace:*",
"@trigger.dev/slack": "workspace:*",
"@trigger.dev/typeform": "workspace:*",
"@trigger.dev/eslint-plugin": "workspace:*",
"@types/node": "18.15.13",
"@types/react": "18.2.17",
"@types/react-dom": "18.2.7",
Expand Down
23 changes: 23 additions & 0 deletions packages/eslint-plugin/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use strict";

module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:eslint-plugin/recommended",
"plugin:node/recommended",
],
env: {
node: true,
},
overrides: [
{
files: ["tests/**/*.js"],
env: { mocha: true },
},
],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
};
52 changes: 52 additions & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @trigger.dev/eslint-plugin

ESLint plugin with trigger.dev best practices

## Installation

You'll first need to install [ESLint](https://eslint.org/):

```sh
npm i eslint --save-dev
```

Next, install `@trigger.dev/eslint-plugin`:

```sh
npm install @trigger.dev/eslint-plugin --save-dev
```

## Usage

Add `trigger-dev` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

```json
{
"plugins": [
"trigger-dev"
]
}
```


Then configure the rules you want to use under the rules section.

```json
{
"rules": {
"trigger-dev/rule-name": 2
}
}
```

## Rules

<!-- begin auto-generated rules list -->

| Name | Description |
| :--------------------------------------------------------------- | :----------------------------------------------- |
| [no-duplicated-task-keys](docs/rules/no-duplicated-task-keys.md) | Prevent duplicated task keys on trigger.dev jobs |

<!-- end auto-generated rules list -->


37 changes: 37 additions & 0 deletions packages/eslint-plugin/docs/rules/no-duplicated-task-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Prevent duplicated task keys on trigger.dev jobs (`trigger-dev/no-duplicated-task-keys`)

<!-- end auto-generated rule header -->

Please describe the origin of the rule here.

## Rule Details

This rule aims to...

Examples of **incorrect** code for this rule:

```js

// fill me in

```

Examples of **correct** code for this rule:

```js

// fill me in

```

### Options

If there are any options, describe them here. Otherwise, delete this section.

## When Not To Use It

Give a short description of when it would be appropriate to turn off this rule.

## Further Reading

If there are other links that describe the issue this rule addresses, please include them here in a bulleted list.
22 changes: 22 additions & 0 deletions packages/eslint-plugin/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @fileoverview ESLint plugin with trigger.dev best practices
* @author
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const requireIndex = require("requireindex");

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------


// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + "/rules");



110 changes: 110 additions & 0 deletions packages/eslint-plugin/lib/rules/no-duplicated-task-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @fileoverview Prevent duplicated task keys on trigger.dev jobs
* @author
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem', // `problem`, `suggestion`, or `layout`
docs: {
description: "Prevent duplicated task keys on trigger.dev jobs",
recommended: true,
url: null, // URL to the documentation page for this rule
},
fixable: null, // Or `code` or `whitespace`
schema: [], // Add a schema if the rule has options
messages: {
duplicatedTaskKey: "Task key '{{taskKey}}' is duplicated"
}
},

create(context) {
const getArguments = (node) => node.arguments || node.argument.arguments;

const getKey = (node) => {
const args = getArguments(node);

const key = args.find((arg) => arg.type === 'Literal');

if (!key) return;

return key.value;
}

const getTaskName = (expression) => {
const callee = expression.callee || expression.argument.callee;

const property = callee.property;

// We need property to be an Identifier, otherwise it's not a task
if (property.type !== 'Identifier') return;

// for io.slack.postMessage, postMessage
return property.name;
}

const groupExpressionsByTask = (ExpressionStatements, map = new Map()) => ExpressionStatements.reduce((acc, { expression }) => {
const taskName = getTaskName(expression);
const taskKey = getKey(expression);

if (acc.has(taskName)) {
acc.get(taskName).push(taskKey);
} else {
acc.set(taskName, [taskKey]);
}

return acc;
}, map);

const groupVariableDeclarationsByTask = VariableDeclarations => VariableDeclarations.reduce((acc, { declarations }) => {
declarations.forEach((declaration) => {
if (!['AwaitExpression', 'CallExpression'].includes(declaration.init.type)) return;

const taskName = getTaskName(declaration.init);

const taskKey = getKey(declaration.init);

if (acc.has(taskName)) {
acc.get(taskName).push(taskKey);
} else {
acc.set(taskName, [taskKey]);
}
});

return acc;
}, new Map());

return {
"CallExpression[callee.property.name='defineJob'] ObjectExpression BlockStatement": (node) => {
const VariableDeclarations = node.body.filter((arg) => arg.type === 'VariableDeclaration');

const grouped = groupVariableDeclarationsByTask(VariableDeclarations);

const ExpressionStatements = node.body.filter((arg) => arg.type === 'ExpressionStatement');

// it'll be a map of taskName => [key1, key2, ...]
const groupedByTask = groupExpressionsByTask(ExpressionStatements, grouped);

groupedByTask.forEach((keys) => {
const duplicated = keys.find((key, index) => keys.indexOf(key) !== index);

if (duplicated) {
context.report({
node,
messageId: 'duplicatedTaskKey',
data: {
taskKey: duplicated
},
});
}
})
}
}
}
};
38 changes: 38 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@trigger.dev/eslint-plugin",
"version": "2.0.9",
"description": "ESLint plugin with trigger.dev best practices",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin"
],
"author": "",
"main": "./lib/index.js",
"exports": "./lib/index.js",
"scripts": {
"lint": "npm-run-all \"lint:*\"",
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
"lint:js": "eslint .",
"test": "mocha tests --recursive",
"update:eslint-docs": "eslint-doc-generator"
},
"dependencies": {
"requireindex": "^1.2.0"
},
"devDependencies": {
"eslint": "^8.19.0",
"eslint-doc-generator": "^1.0.0",
"eslint-plugin-eslint-plugin": "^5.0.0",
"eslint-plugin-node": "^11.1.0",
"mocha": "^10.0.0",
"npm-run-all": "^4.1.5"
},
"engines": {
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
},
"peerDependencies": {
"eslint": ">=7"
},
"license": "ISC"
}
Loading