Skip to content

Commit ad87d0d

Browse files
committed
chore: lint custom ts/no-arrow-parameter-types
1 parent 4083c03 commit ad87d0d

File tree

9 files changed

+152
-21
lines changed

9 files changed

+152
-21
lines changed

eslint.config.ts

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default defineConfig(
4242
{
4343
name: 'eslint overrides',
4444
rules: {
45+
curly: ['error', 'all'],
4546
eqeqeq: ['error', 'always', { null: 'ignore' }],
4647
'logical-assignment-operators': 'error',
4748
'no-else-return': 'error',
@@ -265,6 +266,127 @@ export default defineConfig(
265266
'@typescript-eslint/no-throw-literal': 'off',
266267
'unicorn/no-array-reduce': 'off',
267268
},
268-
}
269+
},
269270
//#endregion
271+
272+
{
273+
name: 'custom',
274+
plugins: {
275+
custom: {
276+
rules: {
277+
'no-arrow-parameter-types': {
278+
meta: {
279+
fixable: 'code',
280+
hasSuggestions: true,
281+
type: 'suggestion',
282+
dialects: ['typescript'],
283+
schema: [
284+
{
285+
type: 'object',
286+
properties: {
287+
allowOptional: {
288+
type: 'boolean',
289+
default: false,
290+
description:
291+
'Allow type annotations when the parameter is optional. Sometimes useful for overloaded functions.',
292+
},
293+
},
294+
},
295+
],
296+
defaultOptions: [
297+
{
298+
allowOptional: false,
299+
},
300+
],
301+
},
302+
create(context) {
303+
const options = context.options[0] as { allowOptional: boolean };
304+
305+
return {
306+
ArrowFunctionExpression(node) {
307+
const paramsWithTypeAnnotation = node.params.filter(
308+
(
309+
// @ts-expect-error: will be inferred when moved into an official plugin
310+
param
311+
) => param.typeAnnotation !== undefined
312+
);
313+
314+
const isCatchClause =
315+
node.parent.callee?.property?.name === 'catch';
316+
317+
if (paramsWithTypeAnnotation.length > 0 && !isCatchClause) {
318+
for (const param of paramsWithTypeAnnotation) {
319+
if (param.optional && options.allowOptional) {
320+
continue;
321+
}
322+
323+
context.report({
324+
node: param,
325+
message:
326+
'Arrow function parameters should not have type annotations. Instead the Object where the operation is used should be typed correctly.',
327+
fix(fixer) {
328+
if (param.optional) {
329+
return null;
330+
}
331+
332+
// TODO @Shinigami92 2025-06-16: Handle async arrow functions
333+
if (node.parent.type === 'VariableDeclarator') {
334+
const variableDeclaratorNode = node.parent;
335+
336+
return [
337+
// Remove ` =>`
338+
fixer.replaceTextRange(
339+
[node.body.range[0] - 3, node.body.range[0]],
340+
''
341+
),
342+
// Remove ` = `
343+
fixer.replaceTextRange(
344+
[
345+
variableDeclaratorNode.id.range[1],
346+
variableDeclaratorNode.init.range[0],
347+
],
348+
''
349+
),
350+
// Replace `const ` with `function `
351+
fixer.replaceTextRange(
352+
[
353+
variableDeclaratorNode.parent.range[0],
354+
variableDeclaratorNode.range[0],
355+
],
356+
'function '
357+
),
358+
];
359+
}
360+
361+
return fixer.removeRange(param.typeAnnotation.range);
362+
},
363+
suggest: [
364+
{
365+
desc: 'Remove type annotation',
366+
fix(fixer) {
367+
if (param.optional) {
368+
return fixer.removeRange([
369+
param.typeAnnotation.range[0] - 1, // Remove the `?` before the type annotation
370+
param.typeAnnotation.range[1],
371+
]);
372+
}
373+
374+
return null;
375+
},
376+
},
377+
],
378+
});
379+
}
380+
}
381+
},
382+
};
383+
},
384+
},
385+
},
386+
},
387+
},
388+
rules: {
389+
'custom/no-arrow-parameter-types': ['error', { allowOptional: true }],
390+
},
391+
}
270392
);

src/db.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function db(
106106
});
107107

108108
const query: DBConnection['query'] = async (
109-
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
109+
queryTextOrConfig,
110110
values?: any[]
111111
): Promise<QueryArrayResult | QueryResult> => {
112112
await createConnection();
@@ -147,20 +147,23 @@ ${error}
147147
};
148148

149149
const select: DBConnection['select'] = async (
150-
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
150+
queryTextOrConfig,
151151
values?: any[]
152152
) => {
153153
const { rows } = await query(queryTextOrConfig, values);
154154
return rows;
155155
};
156156

157157
const column: DBConnection['column'] = async (
158-
columnName: string,
159-
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
158+
columnName,
159+
queryTextOrConfig,
160160
values?: any[]
161161
) => {
162-
const rows = await select(queryTextOrConfig, values);
163-
return rows.map((r: { [key: string]: any }) => r[columnName]);
162+
const rows: Array<{ [key: string]: any }> = await select(
163+
queryTextOrConfig,
164+
values
165+
);
166+
return rows.map((r) => r[columnName]);
164167
};
165168

166169
return {

src/migrationBuilder.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -825,9 +825,11 @@ export class MigrationBuilder {
825825
// This function wraps each operation within a function that either calls
826826
// the operation or its reverse, and appends the result
827827
// (array of sql statements) to the steps array
828-
const wrap =
829-
<TOperation extends Operation>(operation: TOperation) =>
830-
(...args: Parameters<TOperation>) => {
828+
const wrap: <TOperation extends Operation>(
829+
operation: TOperation
830+
) => (...args: Parameters<TOperation>) => void =
831+
(operation) =>
832+
(...args) => {
831833
if (this._REVERSE_MODE) {
832834
if (typeof operation.reverse !== 'function') {
833835
const name = `pgm.${operation.name}()`;
@@ -970,9 +972,11 @@ export class MigrationBuilder {
970972

971973
// Expose DB so we can access database within transaction
972974
/* eslint-disable @typescript-eslint/no-explicit-any */
973-
const wrapDB =
974-
<T extends any[], TResult>(operation: (...args: T) => TResult) =>
975-
(...args: T) => {
975+
const wrapDB: <T extends any[], TResult>(
976+
operation: (...args: T) => TResult
977+
) => (...args: T) => TResult =
978+
(operation) =>
979+
(...args) => {
976980
if (this._REVERSE_MODE) {
977981
throw new Error('Impossible to automatically infer down migration');
978982
}

src/operations/operators/addToOperatorFamily.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ export type AddToOperatorFamilyFn = (
1212

1313
export type AddToOperatorFamily = Reversible<AddToOperatorFamilyFn>;
1414

15-
export const addToOperatorFamily = (
15+
export const addToOperatorFamily: (
1616
mOptions: MigrationOptions
17-
): AddToOperatorFamily => {
17+
) => AddToOperatorFamily = (mOptions) => {
1818
const method: AddToOperatorFamily = (
1919
operatorFamilyName,
2020
indexMethod,

src/operations/operators/removeFromOperatorFamily.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ export type RemoveFromOperatorFamily = (
99
operatorList: OperatorListDefinition[]
1010
) => string;
1111

12-
export const removeFromOperatorFamily = (
12+
export const removeFromOperatorFamily: (
1313
mOptions: MigrationOptions
14-
): RemoveFromOperatorFamily => {
14+
) => RemoveFromOperatorFamily = (mOptions) => {
1515
const method: RemoveFromOperatorFamily = (
1616
operatorFamilyName,
1717
indexMethod,

src/operations/schemas/createSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type CreateSchemaFn = (
1515
export type CreateSchema = Reversible<CreateSchemaFn>;
1616

1717
export function createSchema(mOptions: MigrationOptions): CreateSchema {
18-
const _create: CreateSchema = (schemaName: string, options = {}) => {
18+
const _create: CreateSchema = (schemaName, options = {}) => {
1919
const { ifNotExists = false, authorization } = options;
2020

2121
const ifNotExistsStr = ifNotExists ? ' IF NOT EXISTS' : '';

src/operations/tables/shared.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,10 @@ export function parseLike(
439439
like: Name | { table: Name; options?: LikeOptions },
440440
literal: Literal
441441
): string {
442-
const formatOptions = (
442+
const formatOptions: (
443443
name: 'INCLUDING' | 'EXCLUDING',
444444
options?: Like | Like[]
445-
) =>
445+
) => string = (name, options) =>
446446
toArray(options)
447447
.filter((option): option is Like => option !== undefined)
448448
.map((option) => ` ${name} ${option}`)

src/operations/triggers/createTrigger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ export type CreateTrigger = Reversible<CreateTriggerFn>;
2626

2727
export function createTrigger(mOptions: MigrationOptions): CreateTrigger {
2828
const _create: CreateTrigger = (
29+
/* eslint-disable custom/no-arrow-parameter-types */
2930
tableName: Name,
3031
triggerName: string,
3132
triggerOptions:
3233
| (TriggerOptions & DropOptions)
3334
| (TriggerOptions & FunctionOptions & DropOptions),
3435
definition?: Value
36+
/* eslint-enable custom/no-arrow-parameter-types */
3537
) => {
3638
const {
3739
constraint = false,

test/ts/customRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Options =
1313
| ({ databaseUrl: string } & TestOptions)
1414
| ({ dbClient: Client } & TestOptions);
1515

16-
export const run = async (options: Options): Promise<boolean> => {
16+
export const run: (options: Options) => Promise<boolean> = async (options) => {
1717
const opts: Omit<RunnerOption, 'direction'> & Options = {
1818
migrationsTable: 'migrations',
1919
dir: resolve(import.meta.dirname, 'migrations'),

0 commit comments

Comments
 (0)