Skip to content

Commit 04ba040

Browse files
authored
chore: add exported-function-args-maximum Deno Style Guide linter plugin (#6561)
1 parent ce1ee56 commit 04ba040

11 files changed

+141
-0
lines changed

_tools/lint_plugin.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88

99
import { toCamelCase, toPascalCase } from "@std/text";
10+
import { toFileUrl } from "@std/path/to-file-url";
11+
import { resolve } from "@std/path/resolve";
1012

1113
const PASCAL_CASE_REGEXP = /^_?(?:[A-Z][a-z0-9]*)*_?$/;
1214
const UPPER_CASE_ONLY = /^_?[A-Z]{2,}$/;
@@ -310,5 +312,69 @@ export default {
310312
};
311313
},
312314
},
315+
// https://docs.deno.com/runtime/contributing/style_guide/#exported-functions%3A-max-2-args%2C-put-the-rest-into-an-options-object
316+
"exported-function-args-maximum": {
317+
create(context) {
318+
const url = toFileUrl(resolve(context.filename));
319+
320+
// assertions and testing utils generally don't follow this rule
321+
if (url.href.includes("/assert/")) return {};
322+
if (url.href.includes("/testing/mock.ts")) return {};
323+
if (url.href.includes("/testing/unstable_stub.ts")) return {};
324+
if (url.href.includes("/testing/snapshot.ts")) return {};
325+
326+
// exports from private utils don't need to follow this rule
327+
if (url.href.includes("/_")) return {};
328+
// internal exports don't need to follow this rule
329+
if (url.href.includes("/internal/")) return {};
330+
// bytes API generally don't follow this rule
331+
if (url.href.includes("/bytes/")) return {};
332+
333+
return {
334+
ExportNamedDeclaration(node) {
335+
if (node.declaration?.type !== "FunctionDeclaration") return;
336+
const { params, id } = node.declaration;
337+
if (params.length < 3) return;
338+
if (params.length === 3) {
339+
const param = params.at(-1)!;
340+
341+
switch (param.type) {
342+
case "Identifier":
343+
if (param.name === "options") return;
344+
// Function as 3rd argument is valid (e.g. pooledMap)
345+
if (
346+
param.typeAnnotation?.typeAnnotation?.type ===
347+
"TSFunctionType"
348+
) return;
349+
// attributes: Pick<T, "foo" | "bar"> as 3rd argument is valid
350+
if (
351+
param.typeAnnotation?.typeAnnotation?.typeName?.name ===
352+
"Pick"
353+
) return;
354+
break;
355+
case "AssignmentPattern": {
356+
if (param.right.type === "ObjectExpression") return;
357+
break;
358+
}
359+
}
360+
361+
return context.report({
362+
node: id ?? declaration,
363+
message:
364+
"Third argument of export function is not an options object or function.",
365+
hint:
366+
"Export functions can have 0-2 required arguments, plus (if necessary) an options object (so max 3 total).",
367+
});
368+
}
369+
context.report({
370+
node: id ?? declaration,
371+
message: "Exported function has more than three arguments.",
372+
hint:
373+
"Export functions can have 0-2 required arguments, plus (if necessary) an options object (so max 3 total).",
374+
});
375+
},
376+
};
377+
},
378+
},
313379
},
314380
} satisfies Deno.lint.Plugin;

_tools/lint_plugin_test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,67 @@ new CustomError("Can't parse input");
310310
],
311311
);
312312
});
313+
314+
Deno.test("deno-style-guide/exported-function-args-maximum", {
315+
ignore: !Deno.version.deno.startsWith("2"),
316+
}, () => {
317+
// Good
318+
assertLintPluginDiagnostics(
319+
`
320+
function foo() {
321+
}
322+
function foo(bar: unknown) {
323+
}
324+
function foo(bar: unknown, baz: unknown) {
325+
}
326+
function foo(bar: unknown, baz: unknown, options: Record<string, unknown>) {
327+
}
328+
function foo(bar: unknown, baz: unknown, bat: unknown, bay: unknown) {
329+
}
330+
export function foo() {
331+
}
332+
export function foo(bar: unknown) {
333+
}
334+
export function foo(bar: unknown, baz: unknown) {
335+
}
336+
export function foo(bar: unknown, baz: unknown, options: Record<string, unknown>) {
337+
}
338+
// function as last argument is allowed
339+
export function foo(bar: unknown, baz: unknown, bat: () => unknown) {
340+
}
341+
// Pick<T> is usually an object
342+
export function foo(bar: unknown, baz: unknown, bat: Pick<SomeType, "foo">) {
343+
}
344+
`,
345+
[],
346+
);
347+
348+
// Bad
349+
assertLintPluginDiagnostics(
350+
`
351+
export function foo(bar: unknown, baz: unknown, bat: unknown) {
352+
}
353+
export function foo(bar: unknown, baz: unknown, bat: unknown, options: Record<string, unknown>) {
354+
}
355+
`,
356+
[
357+
{
358+
fix: [],
359+
hint:
360+
"Export functions can have 0-2 required arguments, plus (if necessary) an options object (so max 3 total).",
361+
id: "deno-style-guide/exported-function-args-maximum",
362+
message:
363+
"Third argument of export function is not an options object or function.",
364+
range: [17, 20],
365+
},
366+
{
367+
fix: [],
368+
hint:
369+
"Export functions can have 0-2 required arguments, plus (if necessary) an options object (so max 3 total).",
370+
id: "deno-style-guide/exported-function-args-maximum",
371+
message: "Exported function has more than three arguments.",
372+
range: [83, 86],
373+
},
374+
],
375+
);
376+
});

collections/reduce_groups.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { mapValues } from "./map_values.ts";
3636
* });
3737
* ```
3838
*/
39+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
3940
export function reduceGroups<T, A>(
4041
record: Readonly<Record<string, ReadonlyArray<T>>>,
4142
reducer: (accumulator: A, current: T) => A,

collections/running_reduce.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* assertEquals(sumSteps, [1, 3, 6, 10, 15]);
2727
* ```
2828
*/
29+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
2930
export function runningReduce<T, O>(
3031
array: readonly T[],
3132
reducer: (accumulator: O, current: T, currentIndex: number) => O,

encoding/varint.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export function decodeVarint32(buf: Uint8Array, offset = 0): [number, number] {
215215
* assertEquals(encodeVarint(42n, buf), [new Uint8Array([42]), 1]);
216216
* ```
217217
*/
218+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
218219
export function encodeVarint(
219220
num: bigint | number,
220221
buf: Uint8Array = new Uint8Array(MaxVarintLen64),

fs/unstable_chown.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { mapError } from "./_map_error.ts";
2424
* @param uid The user id (UID) of the new owner, or `null` for no change.
2525
* @param gid The group id (GID) of the new owner, or `null` for no change.
2626
*/
27+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
2728
export async function chown(
2829
path: string | URL,
2930
uid: number | null,
@@ -61,6 +62,7 @@ export async function chown(
6162
* @param uid The user id (UID) of the new owner, or `null` for no change.
6263
* @param gid The group id (GID) of the new owner, or `null` for no change.
6364
*/
65+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
6466
export function chownSync(
6567
path: string | URL,
6668
uid: number | null,

fs/unstable_utime.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { mapError } from "./_map_error.ts";
3232
* @param atime The new access time
3333
* @param mtime The new modification time
3434
*/
35+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
3536
export async function utime(
3637
path: string | URL,
3738
atime: number | Date,
@@ -78,6 +79,7 @@ export async function utime(
7879
* @param atime The new access time
7980
* @param mtime The new modification time
8081
*/
82+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
8183
export function utimeSync(
8284
path: string | URL,
8385
atime: number | Date,

streams/to_transform_stream.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
* );
6161
* ```
6262
*/
63+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
6364
export function toTransformStream<I, O>(
6465
transformer: (src: ReadableStream<I>) => Iterable<O> | AsyncIterable<O>,
6566
writableStrategy?: QueuingStrategy<I>,

webgpu/create_capture.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface CreateCapture {
7070
* @param height The height of the capture texture.
7171
* @returns The texture to render to and buffer to read from.
7272
*/
73+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
7374
export function createCapture(
7475
device: GPUDevice,
7576
width: number,

webgpu/row_padding.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export function getRowPadding(width: number): Padding {
7070
* @param height The height of the output buffer.
7171
* @returns The resliced buffer.
7272
*/
73+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
7374
export function resliceBufferWithPadding(
7475
buffer: Uint8Array,
7576
width: number,

webgpu/texture_with_data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ function textureMipLevelSize(
120120
* @param data The data to write to the texture.
121121
* @returns The newly created texture.
122122
*/
123+
// deno-lint-ignore deno-style-guide/exported-function-args-maximum
123124
export function createTextureWithData(
124125
device: GPUDevice,
125126
descriptor: GPUTextureDescriptor,

0 commit comments

Comments
 (0)