From e0a7037b65dfe3b0e69e1484ef9b1e80547901e8 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Wed, 27 Nov 2024 22:15:24 +0800 Subject: [PATCH] feat: new command `no-x-above` --- src/commands/index.ts | 3 ++ src/commands/no-x-above.md | 30 ++++++++++++++++++ src/commands/no-x-above.test.ts | 44 ++++++++++++++++++++++++++ src/commands/no-x-above.ts | 56 +++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 src/commands/no-x-above.md create mode 100644 src/commands/no-x-above.test.ts create mode 100644 src/commands/no-x-above.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index e0b6614..4f5c2b0 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -4,6 +4,7 @@ import { keepSorted } from './keep-sorted' import { keepUnique } from './keep-unique' import { noShorthand } from './no-shorthand' import { noType } from './no-type' +import { noXAbove } from './no-x-above' import { regex101 } from './regex101' import { toArrow } from './to-arrow' import { toDestructuring } from './to-destructuring' @@ -24,6 +25,7 @@ export { keepUnique, noShorthand, noType, + noXAbove, regex101, toArrow, toDestructuring, @@ -45,6 +47,7 @@ export const builtinCommands = [ keepUnique, noShorthand, noType, + noXAbove, regex101, toArrow, toDestructuring, diff --git a/src/commands/no-x-above.md b/src/commands/no-x-above.md new file mode 100644 index 0000000..e0c9eef --- /dev/null +++ b/src/commands/no-x-above.md @@ -0,0 +1,30 @@ +# `no-x-above` + +Disallow certain syntaxes above or below the comment, with in the current block. + +## Triggers + +- `// @no-await-above` - Disallow `await` above the comment. +- `// @no-await-below` - Disallow `await` below the comment. + +## Examples + +```js +const foo = syncOp() +const bar = await asyncOp() // <-- this is not allowed +// @no-await-above +const baz = await asyncOp() // <-- this is ok +``` + +The effect will only affect the current scope, for example: + +```js +console.log(await foo()) // <-- this is not checked, as it's not in the function scope where the comment is + +async function foo() { + const bar = syncOp() + const baz = await asyncOp() // <-- this is not allowed + // @no-await-above + const qux = await asyncOp() // <-- this is ok +} +``` diff --git a/src/commands/no-x-above.test.ts b/src/commands/no-x-above.test.ts new file mode 100644 index 0000000..555a7b2 --- /dev/null +++ b/src/commands/no-x-above.test.ts @@ -0,0 +1,44 @@ +import { $, run } from './_test-utils' +import { noXAbove as command } from './no-x-above' + +run( + command, + // { + // code: $` + // /// no-await-below + // const obj = await foo() + // `, + // errors: ['command-error'], + // }, + // $` + // const obj = await foo() + // /// no-await-below + // const obj = foo() + // `, + // { + // code: $` + // const obj = await foo() + // /// no-await-above + // const obj = foo() + // `, + // errors: ['command-fix'], + // }, + // // Don't count outside of scope + // $` + // await foo() + // async function foo() { + // /// no-await-above + // const obj = await Promise.all([]) + // } + // `, + // Don't count inside + $` + async function foo() { + /// no-await-below + console.log('foo') + const bar = async () => { + const obj = await Promise.all([]) + } + } + `, +) diff --git a/src/commands/no-x-above.ts b/src/commands/no-x-above.ts new file mode 100644 index 0000000..c1824cc --- /dev/null +++ b/src/commands/no-x-above.ts @@ -0,0 +1,56 @@ +import type { Command } from '../types' + +const types = [ + 'await', + + // TODO: implement + // 'statements', + // 'functions', +] as const + +export const noXAbove: Command = { + name: 'no-x-above', + match: new RegExp(`^\\s*[/:@]\\s*no-(${types.join('|')})-(above|below)$`), + action(ctx) { + const type = ctx.matches[1] as (typeof types)[number] + const direction = ctx.matches[2] as 'above' | 'below' + + const node = ctx.findNodeBelow(() => true) + const parent = node?.parent + if (!parent) + return ctx.reportError('No parent node found') + + if (parent.type !== 'Program' && parent.type !== 'BlockStatement') + return ctx.reportError('Parent node is not a block') + + const children = parent.body + + const targetNodes = direction === 'above' + ? children.filter(c => c.range[1] <= ctx.comment.range[0]) + : children.filter(c => c.range[0] >= ctx.comment.range[1]) + + if (!targetNodes.length) + return + + switch (type) { + case 'await': + for (const target of targetNodes) { + ctx.traverse(target, (path, { SKIP }) => { + if (path.node.type === 'FunctionDeclaration' || path.node.type === 'FunctionExpression' || path.node.type === 'ArrowFunctionExpression') { + return SKIP + } + if (path.node.type === 'AwaitExpression') { + ctx.report({ + node: path.node, + message: 'Disallowed await expression', + }) + } + }) + } + return + default: + return ctx.reportError(`Unknown type: ${type}`) + } + // console.log({ targetRange: targetNodes }) + }, +}