@baseplate-dev/tools 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,13 @@ export const reactEslintConfig = tsEslint.config(
42
42
  'error',
43
43
  { checksVoidReturn: { attributes: false } },
44
44
  ],
45
+ // Allow floating navigate from useNavigate to be handled by the router
46
+ '@typescript-eslint/no-floating-promises': [
47
+ 'error',
48
+ {
49
+ allowForKnownSafeCalls: ['UseNavigateResult'],
50
+ },
51
+ ],
45
52
  },
46
53
  },
47
54
 
@@ -56,6 +63,21 @@ export const reactEslintConfig = tsEslint.config(
56
63
  rules: {
57
64
  // We use replace since it is not supported by ES2020
58
65
  'unicorn/prefer-string-replace-all': 'off',
66
+ // Support kebab case with - prefix to support ignored files in routes
67
+ 'unicorn/filename-case': [
68
+ 'error',
69
+ {
70
+ cases: {
71
+ kebabCase: true,
72
+ },
73
+ ignore: [String.raw`^-[a-z0-9\-\.]+$`],
74
+ },
75
+ ],
59
76
  },
60
77
  },
78
+
79
+ // Global ignores
80
+ {
81
+ ignores: ['**/route-tree.gen.ts'],
82
+ },
61
83
  );
@@ -0,0 +1,138 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * ESLint rule to disallow unused dependencies in `createGeneratorTask`'s `dependencies` object.
5
+ *
6
+ * This rule checks if a dependency declared in the `dependencies` object of a `createGeneratorTask`
7
+ * is actually destructured and used in the `run` function's parameters.
8
+ * It assumes that `no-unused-vars` will catch any unused destructured variables within the `run` function body.
9
+ *
10
+ * @type {import('eslint').Rule.RuleModule}
11
+ */
12
+ export default {
13
+ meta: {
14
+ type: 'suggestion',
15
+ docs: {
16
+ description:
17
+ 'Disallow unused dependencies in createGeneratorTask dependencies object.',
18
+ category: 'Possible Errors',
19
+ recommended: false,
20
+ },
21
+ fixable: 'code', // Indicate that this rule is auto-fixable
22
+ schema: [], // No options for this rule
23
+ messages: {
24
+ unusedDependency:
25
+ 'Dependency "{{dependencyName}}" declared but not destructured in run function.',
26
+ },
27
+ },
28
+ create(context) {
29
+ return {
30
+ CallExpression(node) {
31
+ // Check if this is a call to `createGeneratorTask`
32
+ // The callee can be `createGeneratorTask` directly or `someObject.createGeneratorTask`
33
+ const callee = node.callee;
34
+ let isCreateGeneratorTaskCall = false;
35
+
36
+ if (
37
+ callee.type === 'Identifier' &&
38
+ callee.name === 'createGeneratorTask'
39
+ ) {
40
+ isCreateGeneratorTaskCall = true;
41
+ } else if (
42
+ callee.type === 'MemberExpression' &&
43
+ callee.property.type === 'Identifier' &&
44
+ callee.property.name === 'createGeneratorTask'
45
+ ) {
46
+ isCreateGeneratorTaskCall = true;
47
+ }
48
+
49
+ if (
50
+ isCreateGeneratorTaskCall &&
51
+ node.arguments.length > 0 &&
52
+ node.arguments[0].type === 'ObjectExpression'
53
+ ) {
54
+ const taskConfig = node.arguments[0];
55
+ let dependenciesNode = null;
56
+ let runFunctionNode = null;
57
+
58
+ // Find 'dependencies' and 'run' properties within the task configuration object
59
+ for (const prop of taskConfig.properties) {
60
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
61
+ if (
62
+ prop.key.name === 'dependencies' &&
63
+ prop.value.type === 'ObjectExpression'
64
+ ) {
65
+ dependenciesNode = prop.value;
66
+ } else if (
67
+ prop.key.name === 'run' &&
68
+ (prop.value.type === 'FunctionExpression' ||
69
+ prop.value.type === 'ArrowFunctionExpression')
70
+ ) {
71
+ runFunctionNode = prop.value;
72
+ }
73
+ }
74
+ }
75
+
76
+ if (dependenciesNode && runFunctionNode) {
77
+ const destructuredRunParams = new Set();
78
+ // Check if the run function has parameters and if the first one is an ObjectPattern (destructuring)
79
+ if (
80
+ runFunctionNode.params.length > 0 &&
81
+ runFunctionNode.params[0].type === 'ObjectPattern'
82
+ ) {
83
+ for (const paramProp of runFunctionNode.params[0].properties) {
84
+ // Ensure it's a simple property (e.g., { foo }) not a rest element or nested destructuring
85
+ if (
86
+ paramProp.type === 'Property' &&
87
+ paramProp.key.type === 'Identifier'
88
+ ) {
89
+ destructuredRunParams.add(paramProp.key.name);
90
+ }
91
+ }
92
+ }
93
+
94
+ // Iterate over declared dependencies and check if they are destructured in the run function
95
+ for (const depProp of dependenciesNode.properties) {
96
+ if (
97
+ depProp.type === 'Property' &&
98
+ depProp.key.type === 'Identifier'
99
+ ) {
100
+ const dependencyName = depProp.key.name;
101
+
102
+ // If the dependency is declared but NOT found in the destructured parameters of the run function
103
+ if (!destructuredRunParams.has(dependencyName)) {
104
+ context.report({
105
+ node: depProp, // Report on the specific dependency property
106
+ messageId: 'unusedDependency',
107
+ data: { dependencyName },
108
+ fix(fixer) {
109
+ // Remove the entire property from the dependencies object
110
+ // This handles commas and whitespace correctly for object properties.
111
+ const sourceCode = context.sourceCode;
112
+ const nextToken = sourceCode.getTokenAfter(depProp);
113
+
114
+ // If there's a comma after this property, remove it too
115
+ if (
116
+ nextToken &&
117
+ nextToken.type === 'Punctuator' &&
118
+ nextToken.value === ',' &&
119
+ depProp.range
120
+ ) {
121
+ return fixer.removeRange([
122
+ depProp.range[0],
123
+ nextToken.range[1],
124
+ ]);
125
+ }
126
+
127
+ return fixer.remove(depProp);
128
+ },
129
+ });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ },
136
+ };
137
+ },
138
+ };
@@ -5,8 +5,11 @@ import vitest from '@vitest/eslint-plugin';
5
5
  import { importX } from 'eslint-plugin-import-x';
6
6
  import perfectionist from 'eslint-plugin-perfectionist';
7
7
  import eslintPluginUnicorn from 'eslint-plugin-unicorn';
8
+ import unusedImports from 'eslint-plugin-unused-imports';
8
9
  import tsEslint from 'typescript-eslint';
9
10
 
11
+ import noUnusedGeneratorDependencies from './rules/no-unused-generator-dependencies.js';
12
+
10
13
  /**
11
14
  * @typedef {Object} GenerateTypescriptEslintConfigOptions
12
15
  * @property {string[]} [extraTsFileGlobs] - Additional file globs to lint with Typescript
@@ -113,6 +116,34 @@ export function generateTypescriptEslintConfig(options = []) {
113
116
  'error',
114
117
  { ignoreTernaryTests: true },
115
118
  ],
119
+
120
+ // Allow redirect and notFound to be thrown from routes (placing in generic config to allow it to be used in *.ts files too)
121
+ '@typescript-eslint/only-throw-error': [
122
+ 'error',
123
+ {
124
+ allow: ['NotFoundError', 'Redirect'],
125
+ },
126
+ ],
127
+ },
128
+ },
129
+
130
+ // Unused Imports Configs
131
+ {
132
+ plugins: {
133
+ 'unused-imports': unusedImports,
134
+ },
135
+ rules: {
136
+ '@typescript-eslint/no-unused-vars': 'off',
137
+ 'unused-imports/no-unused-imports': 'error',
138
+ 'unused-imports/no-unused-vars': [
139
+ 'error',
140
+ {
141
+ vars: 'all',
142
+ varsIgnorePattern: '^_',
143
+ args: 'after-used',
144
+ argsIgnorePattern: '^_',
145
+ },
146
+ ],
116
147
  },
117
148
  },
118
149
 
@@ -236,6 +267,21 @@ export function generateTypescriptEslintConfig(options = []) {
236
267
  },
237
268
  },
238
269
 
270
+ // Baseplate Generator Eslint Rule
271
+ {
272
+ files: ['**/*.generator.ts'],
273
+ plugins: {
274
+ baseplate: {
275
+ rules: {
276
+ 'no-unused-generator-dependencies': noUnusedGeneratorDependencies,
277
+ },
278
+ },
279
+ },
280
+ rules: {
281
+ 'baseplate/no-unused-generator-dependencies': ['error'],
282
+ },
283
+ },
284
+
239
285
  // Global Ignores
240
286
  { ignores: ['dist', 'node_modules'] },
241
287
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baseplate-dev/tools",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Shared dev configurations for linting, formatting, and testing Baseplate projects",
5
5
  "keywords": [
6
6
  "development-tools",
@@ -63,6 +63,7 @@
63
63
  "eslint-plugin-react-hooks": "5.2.0",
64
64
  "eslint-plugin-storybook": "0.12.0",
65
65
  "eslint-plugin-unicorn": "59.0.1",
66
+ "eslint-plugin-unused-imports": "4.1.4",
66
67
  "prettier-plugin-packagejson": "2.5.11",
67
68
  "prettier-plugin-tailwindcss": "0.6.11",
68
69
  "typescript-eslint": "8.32.0"
@@ -4,6 +4,9 @@
4
4
  "display": "Vite React Library",
5
5
  "compilerOptions": {
6
6
  "allowImportingTsExtensions": false,
7
+ /* Make sure we use NodeNext for mixed-Node/React libraries */
8
+ "module": "NodeNext",
9
+ "moduleResolution": "Node16",
7
10
 
8
11
  /* Output */
9
12
  "noEmit": false,