diff --git a/@coven/iterables/async/take.ts b/@coven/iterables/async/take.ts index ae2b816..23bec52 100644 --- a/@coven/iterables/async/take.ts +++ b/@coven/iterables/async/take.ts @@ -28,7 +28,7 @@ export const take = ( yield item; count += 1n; } else { - break; + return; } } } diff --git a/@coven/iterables/async/zip.ts b/@coven/iterables/async/zip.ts index d02a3db..893c0aa 100644 --- a/@coven/iterables/async/zip.ts +++ b/@coven/iterables/async/zip.ts @@ -32,7 +32,7 @@ export const zip = ( .next(); if (done) { - break; + return; } yield [itemFirst as ItemFirst, value] as const; diff --git a/@coven/iterables/zip.ts b/@coven/iterables/zip.ts index bd7828b..41f4a81 100644 --- a/@coven/iterables/zip.ts +++ b/@coven/iterables/zip.ts @@ -28,7 +28,7 @@ export const zip = ( const { done = false, value } = iteratorSecond.next(); if (done) { - break; + return; } yield [itemFirst, value as ItemSecond]; diff --git a/lint/mod.ts b/lint/mod.ts index 8ae4cab..905ef23 100644 --- a/lint/mod.ts +++ b/lint/mod.ts @@ -1,16 +1,37 @@ +import { maxLines } from "./rules/max-lines.ts"; +import { noBreak } from "./rules/no-break.ts"; import { noClass } from "./rules/no-class.ts"; +import { noContinue } from "./rules/no-continue.ts"; +import { noDefaultExport } from "./rules/no-default-export.ts"; +import { noDoWhile } from "./rules/no-do-while.ts"; +import { noEnum } from "./rules/no-enum.ts"; +import { noForIn } from "./rules/no-for-in.ts"; +import { noFunction } from "./rules/no-function.ts"; import { noNull } from "./rules/no-null.ts"; +import { noSwitch } from "./rules/no-switch.ts"; import { noThis } from "./rules/no-this.ts"; import { noThrow } from "./rules/no-throw.ts"; import { noTry } from "./rules/no-try.ts"; +import { noWhile } from "./rules/no-while.ts"; +// deno-lint-ignore coven/no-default-export export default { name: "coven", rules: { + "max-lines": maxLines, + "no-break": noBreak, "no-class": noClass, + "no-continue": noContinue, + "no-default-export": noDefaultExport, + "no-do-while": noDoWhile, + "no-enum": noEnum, + "no-for-in": noForIn, + "no-function": noFunction, "no-null": noNull, + "no-switch": noSwitch, "no-this": noThis, "no-throw": noThrow, "no-try": noTry, + "no-while": noWhile, }, } satisfies Deno.lint.Plugin; diff --git a/lint/no.ts b/lint/no.ts new file mode 100644 index 0000000..2ccc262 --- /dev/null +++ b/lint/no.ts @@ -0,0 +1,26 @@ +/** + * Utility type to figure out what the Node type is based on the Visitor. + */ +type FindContext< + Visitor extends keyof Deno.lint.LintVisitor, + Node extends Deno.lint.Node = Deno.lint.Node, +> = Node extends { type: Visitor } ? Node : never; + +/** + * Shorthand for common "Avoid this, do that." lintinrg rules. + */ +export const no = ( + visitor: Visitor, + message: string, + condition?: ( + data: { context: Deno.lint.RuleContext; node: FindContext }, + ) => boolean, +): Deno.lint.Rule => ({ + create: (context): Deno.lint.LintVisitor => ({ + [visitor]: (node: FindContext): void => { + (condition?.({ context, node }) ?? true) + ? context.report({ message, node }) + : undefined; + }, + }), +}); diff --git a/lint/rules/max-lines.ts b/lint/rules/max-lines.ts new file mode 100644 index 0000000..a34a098 --- /dev/null +++ b/lint/rules/max-lines.ts @@ -0,0 +1,9 @@ +import { no } from "../no.ts"; + +const LINE_LIMIT = 300; + +export const maxLines: Deno.lint.Rule = no( + "Program", + `Avoid large files. Split this into smaller files (${LINE_LIMIT} lines or less).`, + ({ context }) => context.sourceCode.text.split("\n").length > LINE_LIMIT, +); diff --git a/lint/rules/no-break.ts b/lint/rules/no-break.ts new file mode 100644 index 0000000..c546036 --- /dev/null +++ b/lint/rules/no-break.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noBreak: Deno.lint.Rule = no( + "BreakStatement", + "Avoid using `break`. Use explicit control flow.", +); diff --git a/lint/rules/no-class.ts b/lint/rules/no-class.ts index 55e4fe5..ab2a065 100644 --- a/lint/rules/no-class.ts +++ b/lint/rules/no-class.ts @@ -1,10 +1,6 @@ -export const noClass: Deno.lint.Rule = { - create: (context): Deno.lint.LintVisitor => ({ - ClassExpression: (node) => { - context.report({ - node, - message: "Avoid using `class`. Use a function instead.", - }); - }, - }), -}; +import { no } from "../no.ts"; + +export const noClass: Deno.lint.Rule = no( + "ClassExpression", + "Avoid using `class`. Use a function instead.", +); diff --git a/lint/rules/no-continue.ts b/lint/rules/no-continue.ts new file mode 100644 index 0000000..f5c0f1a --- /dev/null +++ b/lint/rules/no-continue.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noContinue: Deno.lint.Rule = no( + "ContinueStatement", + "Avoid using `continue`. Use explicit control flow.", +); diff --git a/lint/rules/no-default-export.ts b/lint/rules/no-default-export.ts new file mode 100644 index 0000000..3129fff --- /dev/null +++ b/lint/rules/no-default-export.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noDefaultExport: Deno.lint.Rule = no( + "ExportDefaultDeclaration", + "Avoid using default exports. Use named exports instead.", +); diff --git a/lint/rules/no-do-while.ts b/lint/rules/no-do-while.ts new file mode 100644 index 0000000..f0a6387 --- /dev/null +++ b/lint/rules/no-do-while.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noDoWhile: Deno.lint.Rule = no( + "DoWhileStatement", + "Avoid using `do..while`. Use utils from `@coven/iterables` or any modern looping method.", +); diff --git a/lint/rules/no-enum.ts b/lint/rules/no-enum.ts new file mode 100644 index 0000000..e9a3032 --- /dev/null +++ b/lint/rules/no-enum.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noEnum: Deno.lint.Rule = no( + "TSEnumDeclaration", + "Avoid using `enum`. Use union types or object literals instead.", +); diff --git a/lint/rules/no-for-in.ts b/lint/rules/no-for-in.ts new file mode 100644 index 0000000..e732272 --- /dev/null +++ b/lint/rules/no-for-in.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noForIn: Deno.lint.Rule = no( + "ForInStatement", + "Avoid using `for..in`. Use utils from `@coven/iterables` or any modern looping method.", +); diff --git a/lint/rules/no-function.ts b/lint/rules/no-function.ts new file mode 100644 index 0000000..9d6090b --- /dev/null +++ b/lint/rules/no-function.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noFunction: Deno.lint.Rule = no( + "FunctionDeclaration", + "Avoid using `function`. Use an arrow expression instead.", +); diff --git a/lint/rules/no-null.ts b/lint/rules/no-null.ts index 1f972c3..f87f190 100644 --- a/lint/rules/no-null.ts +++ b/lint/rules/no-null.ts @@ -1,3 +1,5 @@ +import { no } from "../no.ts"; + /** * Checks for `Object.create`. */ @@ -11,14 +13,8 @@ const isObjectCreate = ( && node.callee.property.type === "Identifier" && node.callee.property.name === "create"; -export const noNull: Deno.lint.Rule = { - create: (context): Deno.lint.LintVisitor => ({ - Literal: (node): void => - (node.raw === "null" && !isObjectCreate(node.parent)) - ? context.report({ - node, - message: "Use `undefined` instead of `null`.", - }) - : undefined, - }), -}; +export const noNull: Deno.lint.Rule = no( + "Literal", + "Avoid using `null`. Use `undefined` instead.", + ({ node }) => node.raw === "null" && !isObjectCreate(node.parent), +); diff --git a/lint/rules/no-switch.ts b/lint/rules/no-switch.ts new file mode 100644 index 0000000..361c1c5 --- /dev/null +++ b/lint/rules/no-switch.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noSwitch: Deno.lint.Rule = no( + "SwitchStatement", + "Avoid using `switch`. Use a dictionary instead.", +); diff --git a/lint/rules/no-this.ts b/lint/rules/no-this.ts index 5194b92..3712562 100644 --- a/lint/rules/no-this.ts +++ b/lint/rules/no-this.ts @@ -1,10 +1,6 @@ -export const noThis: Deno.lint.Rule = { - create: (context): Deno.lint.LintVisitor => ({ - ThisExpression: (node) => { - context.report({ - node, - message: "Avoid using `this`. Use predictable values instead.", - }); - }, - }), -}; +import { no } from "../no.ts"; + +export const noThis: Deno.lint.Rule = no( + "ThisExpression", + "Avoid using `this`. Use explicit values instead.", +); diff --git a/lint/rules/no-throw.ts b/lint/rules/no-throw.ts index 77db772..e70ca01 100644 --- a/lint/rules/no-throw.ts +++ b/lint/rules/no-throw.ts @@ -1,9 +1,6 @@ -export const noThrow: Deno.lint.Rule = { - create: (context): Deno.lint.LintVisitor => ({ - ThrowStatement: (node): void => - context.report({ - node, - message: "Avoid using `throw`. Return the error instead.", - }), - }), -}; +import { no } from "../no.ts"; + +export const noThrow: Deno.lint.Rule = no( + "ThrowStatement", + "Avoid using `throw`. Return the error instead.", +); diff --git a/lint/rules/no-try.ts b/lint/rules/no-try.ts index 8359e08..2c7d9ed 100644 --- a/lint/rules/no-try.ts +++ b/lint/rules/no-try.ts @@ -1,10 +1,6 @@ -export const noTry: Deno.lint.Rule = { - create: (context): Deno.lint.LintVisitor => ({ - TryStatement: (node): void => - context.report({ - node, - message: - "Avoid `try`/`catch`. Use `attempt` from `@coven/parsers`, or a Promise.", - }), - }), -}; +import { no } from "../no.ts"; + +export const noTry: Deno.lint.Rule = no( + "TryStatement", + "Avoid using `try`/`catch`. Use `attempt` from `@coven/parsers`, or a Promise.", +); diff --git a/lint/rules/no-while.ts b/lint/rules/no-while.ts new file mode 100644 index 0000000..dc3a130 --- /dev/null +++ b/lint/rules/no-while.ts @@ -0,0 +1,6 @@ +import { no } from "../no.ts"; + +export const noWhile: Deno.lint.Rule = no( + "WhileStatement", + "Avoid using `while`. Use utils from `@coven/iterables` or any modern looping method.", +);