eslint-plugin-ocavue 0.2.0 → 0.3.0

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["rule","noImplicitArrowLinebreak","preferWhileTrue"],"sources":["../package.json","../src/rules/no-implicit-arrow-linebreak.ts","../src/rules/prefer-while-true.ts","../src/index.ts"],"sourcesContent":["","import type { Rule } from 'eslint'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'layout',\n fixable: 'code',\n docs: {\n description:\n 'disallow a linebreak between `=>` and an implicit arrow function body, fixing it to a block body with `return`',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#no-implicit-arrow-linebreak',\n },\n messages: {\n linebreak:\n 'Implicit arrow body must not start on a new line. Use a block body with `return` instead.',\n },\n schema: [],\n },\n create(context) {\n const sourceCode = context.sourceCode\n return {\n ArrowFunctionExpression(node) {\n if (node.body.type === 'BlockStatement') {\n return\n }\n\n const arrowToken = sourceCode.getTokenBefore(node.body, {\n filter: (token) => token.type === 'Punctuator' && token.value === '=>',\n })\n if (!arrowToken) {\n return\n }\n const firstBodyToken = sourceCode.getTokenAfter(arrowToken)\n // The last token of the arrow function. When the body is wrapped in\n // parentheses, this is the closing `)`, so the fix keeps the parens.\n const lastToken = sourceCode.getLastToken(node)\n if (!firstBodyToken || !lastToken) {\n return\n }\n if (arrowToken.loc.end.line === firstBodyToken.loc.start.line) {\n return\n }\n\n // The fix rewrites the region between `=>` and the body, so bail out\n // of fixing (but still report) if that region contains comments.\n const canFix = !sourceCode.commentsExistBetween(arrowToken, firstBodyToken)\n\n context.report({\n node: node.body,\n messageId: 'linebreak',\n fix: canFix\n ? (fixer) => {\n const bodyText = sourceCode.text.slice(firstBodyToken.range[0], lastToken.range[1])\n return fixer.replaceTextRange(\n [arrowToken.range[1], lastToken.range[1]],\n ` { return ${bodyText} }`,\n )\n }\n : undefined,\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { Rule } from 'eslint'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description: 'enforce `while (true)` instead of `for (;;)` for infinite loops',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-while-true',\n },\n messages: {\n preferWhileTrue: 'Use `while (true)` instead of `for (;;)`.',\n },\n schema: [],\n },\n create(context) {\n const sourceCode = context.sourceCode\n return {\n ForStatement(node) {\n if (node.init || node.test || node.update) {\n return\n }\n\n const forToken = sourceCode.getFirstToken(node)\n const closingParen = sourceCode.getTokenBefore(node.body)\n if (!forToken || !closingParen) {\n return\n }\n\n // Bail out of fixing (but still report) if the loop head contains\n // comments, so the fix never drops a comment.\n const canFix = !sourceCode.commentsExistBetween(forToken, closingParen)\n\n context.report({\n node,\n loc: { start: forToken.loc.start, end: closingParen.loc.end },\n messageId: 'preferWhileTrue',\n fix: canFix\n ? (fixer) => {\n return fixer.replaceTextRange(\n [forToken.range[0], closingParen.range[1]],\n 'while (true)',\n )\n }\n : undefined,\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { ESLint } from 'eslint'\n\nimport { version } from '../package.json'\n\nimport noImplicitArrowLinebreak from './rules/no-implicit-arrow-linebreak'\nimport preferWhileTrue from './rules/prefer-while-true'\n\nconst plugin = {\n meta: {\n name: 'eslint-plugin-ocavue',\n version,\n },\n rules: {\n 'no-implicit-arrow-linebreak': noImplicitArrowLinebreak,\n 'prefer-while-true': preferWhileTrue,\n },\n} satisfies ESLint.Plugin\n\nexport default plugin\n"],"mappings":";;AGOA,MAAM,SAAS;CACb,MAAM;EACJ,MAAM;EACN;CACF;CACA,OAAO;EACL,+BAA+BC;GFVjC,MAAM;IACJ,MAAM;IACN,SAAS;IACT,MAAM;KACJ,aACE;KACF,KAAK;IACP;IACA,UAAU,EACR,WACE,4FACJ;IACA,QAAQ,CAAC;GACX;GACA,OAAO,SAAS;IACd,MAAM,aAAa,QAAQ;IAC3B,OAAO,EACL,wBAAwB,MAAM;KAC5B,IAAI,KAAK,KAAK,SAAS,kBACrB;KAGF,MAAM,aAAa,WAAW,eAAe,KAAK,MAAM,EACtD,SAAS,UAAU,MAAM,SAAS,gBAAgB,MAAM,UAAU,KACpE,CAAC;KACD,IAAI,CAAC,YACH;KAEF,MAAM,iBAAiB,WAAW,cAAc,UAAU;KAG1D,MAAM,YAAY,WAAW,aAAa,IAAI;KAC9C,IAAI,CAAC,kBAAkB,CAAC,WACtB;KAEF,IAAI,WAAW,IAAI,IAAI,SAAS,eAAe,IAAI,MAAM,MACvD;KAKF,MAAM,SAAS,CAAC,WAAW,qBAAqB,YAAY,cAAc;KAE1E,QAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACX,KAAK,UACA,UAAU;OACT,MAAM,WAAW,WAAW,KAAK,MAAM,eAAe,MAAM,IAAI,UAAU,MAAM,EAAE;OAClF,OAAO,MAAM,iBACX,CAAC,WAAW,MAAM,IAAI,UAAU,MAAM,EAAE,GACxC,aAAa,SAAS,GACxB;MACF,IACA,KAAA;KACN,CAAC;IACH,EACF;GACF;EEhDiCA;EAC/B,qBAAqBC;GDXvB,MAAM;IACJ,MAAM;IACN,SAAS;IACT,MAAM;KACJ,aAAa;KACb,KAAK;IACP;IACA,UAAU,EACR,iBAAiB,4CACnB;IACA,QAAQ,CAAC;GACX;GACA,OAAO,SAAS;IACd,MAAM,aAAa,QAAQ;IAC3B,OAAO,EACL,aAAa,MAAM;KACjB,IAAI,KAAK,QAAQ,KAAK,QAAQ,KAAK,QACjC;KAGF,MAAM,WAAW,WAAW,cAAc,IAAI;KAC9C,MAAM,eAAe,WAAW,eAAe,KAAK,IAAI;KACxD,IAAI,CAAC,YAAY,CAAC,cAChB;KAKF,MAAM,SAAS,CAAC,WAAW,qBAAqB,UAAU,YAAY;KAEtE,QAAQ,OAAO;MACb;MACA,KAAK;OAAE,OAAO,SAAS,IAAI;OAAO,KAAK,aAAa,IAAI;MAAI;MAC5D,WAAW;MACX,KAAK,UACA,UAAU;OACT,OAAO,MAAM,iBACX,CAAC,SAAS,MAAM,IAAI,aAAa,MAAM,EAAE,GACzC,cACF;MACF,IACA,KAAA;KACN,CAAC;IACH,EACF;GACF;EClCuBA;CACvB;AACF"}
1
+ {"version":3,"file":"index.js","names":["rule","rule","rule","rule","noImplicitArrowLinebreak","preferConstantFunction","preferNoop","preferSleep","preferWhileTrue"],"sources":["../package.json","../src/rules/no-implicit-arrow-linebreak.ts","../src/utils/constant-function.ts","../src/utils/has-dependency.ts","../src/utils/package-import.ts","../src/rules/prefer-constant-function.ts","../src/rules/prefer-noop.ts","../src/rules/prefer-sleep.ts","../src/rules/prefer-while-true.ts","../src/index.ts"],"sourcesContent":["","import type { Rule } from 'eslint'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'layout',\n fixable: 'code',\n docs: {\n description:\n 'disallow a linebreak between `=>` and an implicit arrow function body, fixing it to a block body with `return`',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#no-implicit-arrow-linebreak',\n },\n messages: {\n linebreak:\n 'Implicit arrow body must not start on a new line. Use a block body with `return` instead.',\n },\n schema: [],\n },\n create(context) {\n const sourceCode = context.sourceCode\n return {\n ArrowFunctionExpression(node) {\n if (node.body.type === 'BlockStatement') {\n return\n }\n\n const arrowToken = sourceCode.getTokenBefore(node.body, {\n filter: (token) => token.type === 'Punctuator' && token.value === '=>',\n })\n if (!arrowToken) {\n return\n }\n const firstBodyToken = sourceCode.getTokenAfter(arrowToken)\n // The last token of the arrow function. When the body is wrapped in\n // parentheses, this is the closing `)`, so the fix keeps the parens.\n const lastToken = sourceCode.getLastToken(node)\n if (!firstBodyToken || !lastToken) {\n return\n }\n if (arrowToken.loc.end.line === firstBodyToken.loc.start.line) {\n return\n }\n\n // The fix rewrites the region between `=>` and the body, so bail out\n // of fixing (but still report) if that region contains comments.\n const canFix = !sourceCode.commentsExistBetween(arrowToken, firstBodyToken)\n\n context.report({\n node: node.body,\n messageId: 'linebreak',\n fix: canFix\n ? (fixer) => {\n const bodyText = sourceCode.text.slice(firstBodyToken.range[0], lastToken.range[1])\n return fixer.replaceTextRange(\n [arrowToken.range[1], lastToken.range[1]],\n ` { return ${bodyText} }`,\n )\n }\n : undefined,\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { Rule } from 'eslint'\nimport type { ArrowFunctionExpression, Expression, FunctionExpression, Node } from 'estree'\n\nexport type FunctionNode = (ArrowFunctionExpression | FunctionExpression) & Rule.NodeParentExtension\n\n/**\n * The value that a constant function returns.\n */\nexport type ConstantValue = 'undefined' | 'true' | 'false' | 'null'\n\nfunction getConstantValue(node: Expression): ConstantValue | undefined {\n if (node.type === 'Literal') {\n switch (node.raw) {\n case 'true':\n case 'false':\n case 'null':\n return node.raw\n }\n return undefined\n }\n if (node.type === 'Identifier' && node.name === 'undefined') {\n return 'undefined'\n }\n if (\n node.type === 'UnaryExpression' &&\n node.operator === 'void' &&\n node.argument.type === 'Literal'\n ) {\n return 'undefined'\n }\n return undefined\n}\n\nfunction getReturnedValue(node: FunctionNode): ConstantValue | undefined {\n const body = node.body\n if (body.type !== 'BlockStatement') {\n return getConstantValue(body)\n }\n if (body.body.length === 0) {\n return 'undefined'\n }\n if (body.body.length === 1) {\n const statement = body.body[0]\n if (statement.type === 'ReturnStatement') {\n return statement.argument ? getConstantValue(statement.argument) : 'undefined'\n }\n }\n return undefined\n}\n\n/**\n * Returns true when the function can be swapped with a function reference\n * without changing runtime behavior.\n */\nfunction isReplaceable(node: FunctionNode): boolean {\n if (node.async || node.generator) {\n return false\n }\n if ((node as { typeParameters?: unknown }).typeParameters) {\n return false\n }\n\n // Every parameter must be a plain identifier. Default values may have side\n // effects, and destructuring may throw.\n const hasPlainParams = node.params.every((param) => {\n return (\n param.type === 'Identifier' ||\n (param.type === 'RestElement' && param.argument.type === 'Identifier')\n )\n })\n if (!hasPlainParams) {\n return false\n }\n\n if (node.type === 'FunctionExpression') {\n if (node.id) {\n return false\n }\n const parent: Node = node.parent\n if (parent.type === 'Property' && (parent.method || parent.kind !== 'init')) {\n return false\n }\n if (parent.type === 'MethodDefinition') {\n return false\n }\n if (parent.type === 'NewExpression' && parent.callee === node) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Returns the value that an inline function always returns, such as\n * `'true'` for `() => true` or `'undefined'` for `() => {}`. Returns\n * `undefined` when the function does more than that, or when replacing it\n * could change runtime behavior.\n */\nexport function getConstantFunctionValue(node: FunctionNode): ConstantValue | undefined {\n return isReplaceable(node) ? getReturnedValue(node) : undefined\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\ninterface PackageJson {\n name?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n peerDependencies?: Record<string, string>\n}\n\n// Maps a directory to its nearest `package.json`, or `null` when there is none.\nconst cache = new Map<string, PackageJson | null>()\n\nfunction readPackageJson(dir: string): PackageJson | undefined {\n let text: string\n try {\n text = fs.readFileSync(path.join(dir, 'package.json'), 'utf8')\n } catch {\n return undefined\n }\n try {\n const json: unknown = JSON.parse(text)\n return json && typeof json === 'object' ? json : {}\n } catch {\n return {}\n }\n}\n\nfunction findNearestPackageJson(startDir: string): PackageJson | null {\n const visited: string[] = []\n let dir = startDir\n let result: PackageJson | null = null\n\n while (true) {\n const cached = cache.get(dir)\n if (cached !== undefined) {\n result = cached\n break\n }\n visited.push(dir)\n\n const pkg = readPackageJson(dir)\n if (pkg) {\n result = pkg\n break\n }\n\n const parent = path.dirname(dir)\n if (parent === dir) {\n break\n }\n dir = parent\n }\n\n for (const visitedDir of visited) {\n cache.set(visitedDir, result)\n }\n return result\n}\n\n/**\n * Returns true when the nearest `package.json` of `filename` lists\n * `packageName` in `dependencies`, `devDependencies` or `peerDependencies`.\n *\n * A package never depends on itself, so this returns false inside the\n * package named `packageName`.\n */\nexport function hasDependency(filename: string, packageName: string): boolean {\n if (!path.isAbsolute(filename)) {\n return false\n }\n\n const pkg = findNearestPackageJson(path.dirname(filename))\n if (!pkg || pkg.name === packageName) {\n return false\n }\n\n return [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].some(\n (deps) => deps != null && typeof deps === 'object' && Object.hasOwn(deps, packageName),\n )\n}\n","import type { Rule, Scope } from 'eslint'\nimport type { ImportDeclaration, ImportSpecifier, Node, Program } from 'estree'\n\nimport { hasDependency } from './has-dependency'\n\nexport const UTILS_PACKAGE = '@ocavue/utils'\n\ninterface PackageRuleOptions {\n checkDependency?: boolean\n}\n\nexport const packageRuleSchema: Rule.RuleMetaData['schema'] = [\n {\n type: 'object',\n properties: {\n checkDependency: { type: 'boolean' },\n },\n additionalProperties: false,\n },\n]\n\nfunction getImportKind(node: ImportDeclaration | ImportSpecifier): string | undefined {\n return (node as { importKind?: string }).importKind\n}\n\nfunction getImportedName(specifier: ImportSpecifier): string {\n const imported = specifier.imported\n return imported.type === 'Identifier' ? imported.name : String(imported.value)\n}\n\n/**\n * Returns the value `import` declarations of `packageName` in the program.\n */\nfunction getPackageImports(program: Program, packageName: string): ImportDeclaration[] {\n return program.body.filter((statement): statement is ImportDeclaration => {\n return (\n statement.type === 'ImportDeclaration' &&\n statement.source.value === packageName &&\n getImportKind(statement) !== 'type'\n )\n })\n}\n\n/**\n * Returns true when the linted file can import from `packageName`: the\n * nearest `package.json` depends on it, or the file already imports it. The\n * `checkDependency: false` option skips the check.\n */\nexport function isPackageAvailable(context: Rule.RuleContext, packageName: string): boolean {\n const options = (context.options[0] ?? {}) as PackageRuleOptions\n if (options.checkDependency === false) {\n return true\n }\n return (\n hasDependency(context.physicalFilename, packageName) ||\n getPackageImports(context.sourceCode.ast, packageName).length > 0\n )\n}\n\nexport function findVariable(scope: Scope.Scope | null, name: string): Scope.Variable | undefined {\n for (let current = scope; current; current = current.upper) {\n const variable = current.set.get(name)\n if (variable) {\n return variable\n }\n }\n return undefined\n}\n\ninterface ImportFixOptions {\n context: Rule.RuleContext\n /** The node to replace. */\n node: Node\n /** The named export to import from `packageName`. */\n name: string\n packageName: string\n /** Returns the replacement text, given the local name of the import. */\n replace: (local: string) => string\n}\n\n/**\n * Creates a fix that replaces `node` and makes sure `name` is imported from\n * `packageName`. Returns `undefined` when the fix is not safe: the file is\n * not an ES module, the node holds comments, or another binding already uses\n * the name.\n */\nexport function createImportFix({\n context,\n node,\n name,\n packageName,\n replace,\n}: ImportFixOptions): Rule.ReportFixer | undefined {\n const sourceCode = context.sourceCode\n const program = sourceCode.ast as Program\n\n if (context.languageOptions.sourceType !== 'module') {\n return undefined\n }\n if (sourceCode.getCommentsInside(node).length > 0) {\n return undefined\n }\n\n const scope = sourceCode.getScope(node)\n const packageImports = getPackageImports(program, packageName)\n\n // Reuse an existing import, including an aliased one, when it is visible\n // from the node.\n for (const declaration of packageImports) {\n for (const specifier of declaration.specifiers) {\n if (\n specifier.type !== 'ImportSpecifier' ||\n getImportKind(specifier) === 'type' ||\n getImportedName(specifier) !== name\n ) {\n continue\n }\n const local = specifier.local.name\n const variable = findVariable(scope, local)\n if (variable?.defs[0]?.node === specifier) {\n return (fixer) => fixer.replaceText(node, replace(local))\n }\n }\n }\n\n // Another binding with the same name is in scope.\n if (findVariable(scope, name)) {\n return undefined\n }\n // An undeclared global with the same name is used somewhere in the file.\n const globalScope = sourceCode.scopeManager.globalScope\n if (globalScope?.through.some((reference) => reference.identifier.name === name)) {\n return undefined\n }\n\n const replacement = replace(name)\n\n const namedImport = packageImports.find((declaration) => {\n return declaration.specifiers.some((specifier) => specifier.type === 'ImportSpecifier')\n })\n if (namedImport) {\n const lastSpecifier = namedImport.specifiers.at(-1)!\n return (fixer) => [\n fixer.replaceText(node, replacement),\n fixer.insertTextAfter(lastSpecifier, `, ${name}`),\n ]\n }\n\n const importText = `import { ${name} } from '${packageName}'`\n const lastImport = program.body.findLast((statement) => statement.type === 'ImportDeclaration')\n if (lastImport) {\n const semicolon = sourceCode.getText(lastImport).endsWith(';') ? ';' : ''\n return (fixer) => [\n fixer.replaceText(node, replacement),\n fixer.insertTextAfter(lastImport, `\\n${importText}${semicolon}`),\n ]\n }\n\n // Insert the import after directives such as 'use client'.\n const firstStatement = program.body.find((statement) => {\n // typescript-eslint sets `directive: undefined` on every expression statement.\n return !(\n statement.type === 'ExpressionStatement' &&\n typeof (statement as { directive?: unknown }).directive === 'string'\n )\n })\n if (!firstStatement) {\n return undefined\n }\n return (fixer) => [\n fixer.replaceText(node, replacement),\n fixer.insertTextBefore(firstStatement, `${importText}\\n`),\n ]\n}\n","import type { Rule } from 'eslint'\n\nimport {\n getConstantFunctionValue,\n type ConstantValue,\n type FunctionNode,\n} from '../utils/constant-function'\nimport {\n createImportFix,\n isPackageAvailable,\n packageRuleSchema,\n UTILS_PACKAGE,\n} from '../utils/package-import'\n\nconst FUNCTION_NAMES: Partial<Record<ConstantValue, string>> = {\n true: 'returnTrue',\n false: 'returnFalse',\n null: 'returnNull',\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'enforce `returnTrue`, `returnFalse` and `returnNull` from `@ocavue/utils` instead of inline functions',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-constant-function',\n },\n messages: {\n preferConstantFunction: 'Use `{{name}}` from `@ocavue/utils` instead of an inline function.',\n replaceWithConstantFunction: 'Replace with `{{name}}` from `@ocavue/utils`.',\n },\n schema: packageRuleSchema,\n },\n create(context) {\n if (!isPackageAvailable(context, UTILS_PACKAGE)) {\n return {}\n }\n\n function check(node: FunctionNode): void {\n const value = getConstantFunctionValue(node)\n const name = value && FUNCTION_NAMES[value]\n if (!name) {\n return\n }\n\n // `returnFalse` returns the literal type `false`, while `() => false`\n // returns `boolean`. The swap can narrow inferred types, for example\n // `useState(() => false)`, so it is a suggestion and not an autofix.\n const fix = createImportFix({\n context,\n node,\n name,\n packageName: UTILS_PACKAGE,\n replace: (local) => local,\n })\n context.report({\n node,\n messageId: 'preferConstantFunction',\n data: { name },\n suggest: fix ? [{ messageId: 'replaceWithConstantFunction', data: { name }, fix }] : [],\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default rule\n","import type { Rule } from 'eslint'\n\nimport { getConstantFunctionValue, type FunctionNode } from '../utils/constant-function'\nimport {\n createImportFix,\n isPackageAvailable,\n packageRuleSchema,\n UTILS_PACKAGE,\n} from '../utils/package-import'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description: 'enforce `noop` from `@ocavue/utils` instead of inline empty functions',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-noop',\n },\n messages: {\n preferNoop: 'Use `noop` from `@ocavue/utils` instead of an inline empty function.',\n },\n schema: packageRuleSchema,\n },\n create(context) {\n if (!isPackageAvailable(context, UTILS_PACKAGE)) {\n return {}\n }\n\n function check(node: FunctionNode): void {\n if (getConstantFunctionValue(node) !== 'undefined') {\n return\n }\n context.report({\n node,\n messageId: 'preferNoop',\n fix: createImportFix({\n context,\n node,\n name: 'noop',\n packageName: UTILS_PACKAGE,\n replace: (local) => local,\n }),\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default rule\n","import type { Rule, Scope } from 'eslint'\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n Expression,\n FunctionExpression,\n Node,\n SpreadElement,\n} from 'estree'\n\nimport {\n createImportFix,\n findVariable,\n isPackageAvailable,\n packageRuleSchema,\n UTILS_PACKAGE,\n} from '../utils/package-import'\n\ntype FunctionNode = ArrowFunctionExpression | FunctionExpression\n\nfunction isFunction(node: Node): node is FunctionNode {\n return node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression'\n}\n\n/**\n * Returns true when `name` refers to a global variable, not a local binding.\n */\nfunction isGlobal(scope: Scope.Scope, name: string): boolean {\n const variable = findVariable(scope, name)\n return !variable || variable.defs.length === 0\n}\n\n/**\n * Returns the only expression that a function body evaluates, for bodies\n * such as `x`, `{ x }` and `{ return x }`.\n */\nfunction getOnlyExpression(node: FunctionNode): Expression | undefined {\n const body = node.body\n if (body.type !== 'BlockStatement') {\n return body\n }\n if (body.body.length !== 1) {\n return undefined\n }\n const statement = body.body[0]\n if (statement.type === 'ExpressionStatement') {\n return statement.expression\n }\n if (statement.type === 'ReturnStatement' && statement.argument) {\n return statement.argument\n }\n return undefined\n}\n\nfunction isIdentifier(node: Node | SpreadElement, name: string): boolean {\n return node.type === 'Identifier' && node.name === name\n}\n\n/**\n * Returns true for `setTimeout`, `globalThis.setTimeout` and\n * `window.setTimeout`.\n */\nfunction isSetTimeout(node: Expression, scope: Scope.Scope): boolean {\n if (node.type === 'Identifier') {\n return node.name === 'setTimeout' && isGlobal(scope, 'setTimeout')\n }\n return (\n node.type === 'MemberExpression' &&\n !node.computed &&\n isIdentifier(node.property, 'setTimeout') &&\n node.object.type === 'Identifier' &&\n (node.object.name === 'globalThis' || node.object.name === 'window') &&\n isGlobal(scope, node.object.name)\n )\n}\n\n/**\n * Returns true for `resolve` and `() => resolve()`.\n */\nfunction isResolveCallback(node: Expression | SpreadElement, resolve: string): boolean {\n if (isIdentifier(node, resolve)) {\n return true\n }\n if (!isFunction(node) || node.async || node.generator || node.params.length > 0) {\n return false\n }\n const expression = getOnlyExpression(node)\n return (\n expression?.type === 'CallExpression' &&\n isIdentifier(expression.callee, resolve) &&\n expression.arguments.length === 0\n )\n}\n\n/**\n * Returns true for no type argument, `<void>` and `<undefined>`.\n */\nfunction hasVoidTypeArgument(node: Node): boolean {\n const typeArguments = (node as { typeArguments?: { params: { type: string }[] } }).typeArguments\n if (!typeArguments) {\n return true\n }\n return (\n typeArguments.params.length === 1 &&\n (typeArguments.params[0].type === 'TSVoidKeyword' ||\n typeArguments.params[0].type === 'TSUndefinedKeyword')\n )\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'enforce `sleep` from `@ocavue/utils` instead of `new Promise` with `setTimeout`',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-sleep',\n },\n messages: {\n preferSleep: 'Use `sleep` from `@ocavue/utils` instead of `new Promise` with `setTimeout`.',\n },\n schema: packageRuleSchema,\n },\n create(context) {\n if (!isPackageAvailable(context, UTILS_PACKAGE)) {\n return {}\n }\n\n const sourceCode = context.sourceCode\n\n /**\n * Returns the delay expression of `new Promise((resolve) =>\n * setTimeout(resolve, delay))`, or `undefined` when the node does not\n * match.\n */\n function getDelay(node: Node): Expression | undefined {\n if (\n node.type !== 'NewExpression' ||\n !isIdentifier(node.callee, 'Promise') ||\n node.arguments.length !== 1 ||\n !hasVoidTypeArgument(node)\n ) {\n return undefined\n }\n if (!isGlobal(sourceCode.getScope(node), 'Promise')) {\n return undefined\n }\n\n const executor = node.arguments[0]\n if (!isFunction(executor) || executor.async || executor.generator) {\n return undefined\n }\n if (!executor.params.every((param) => param.type === 'Identifier')) {\n return undefined\n }\n const resolveParam = executor.params[0]\n if (resolveParam?.type !== 'Identifier') {\n return undefined\n }\n\n const call = getOnlyExpression(executor)\n if (call?.type !== 'CallExpression' || call.optional) {\n return undefined\n }\n const executorScope = sourceCode.getScope(executor)\n if (!isSetTimeout(call.callee as Expression, executorScope)) {\n return undefined\n }\n\n const [callback, delay] = (call as CallExpression).arguments\n if (call.arguments.length !== 2 || !delay || delay.type === 'SpreadElement') {\n return undefined\n }\n if (!isResolveCallback(callback, resolveParam.name)) {\n return undefined\n }\n\n // The delay moves out of the executor, so it must not use the\n // executor's parameters, `this` or `arguments`.\n const usesExecutorParams = executorScope.variables.some((variable) => {\n return variable.references.some((reference) => {\n const range = reference.identifier.range!\n return range[0] >= delay.range![0] && range[1] <= delay.range![1]\n })\n })\n if (usesExecutorParams) {\n return undefined\n }\n if (\n executor.type === 'FunctionExpression' &&\n sourceCode.getTokens(delay).some((token) => {\n return token.value === 'this' || token.value === 'arguments'\n })\n ) {\n return undefined\n }\n\n return delay\n }\n\n return {\n NewExpression(node) {\n const delay = getDelay(node)\n if (!delay) {\n return\n }\n const delayText = sourceCode.getText(delay)\n context.report({\n node,\n messageId: 'preferSleep',\n fix: createImportFix({\n context,\n node,\n name: 'sleep',\n packageName: UTILS_PACKAGE,\n replace: (local) => {\n return delay.type === 'SequenceExpression'\n ? `${local}((${delayText}))`\n : `${local}(${delayText})`\n },\n }),\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { Rule } from 'eslint'\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description: 'enforce `while (true)` instead of `for (;;)` for infinite loops',\n url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-while-true',\n },\n messages: {\n preferWhileTrue: 'Use `while (true)` instead of `for (;;)`.',\n },\n schema: [],\n },\n create(context) {\n const sourceCode = context.sourceCode\n return {\n ForStatement(node) {\n if (node.init || node.test || node.update) {\n return\n }\n\n const forToken = sourceCode.getFirstToken(node)\n const closingParen = sourceCode.getTokenBefore(node.body)\n if (!forToken || !closingParen) {\n return\n }\n\n // Bail out of fixing (but still report) if the loop head contains\n // comments, so the fix never drops a comment.\n const canFix = !sourceCode.commentsExistBetween(forToken, closingParen)\n\n context.report({\n node,\n loc: { start: forToken.loc.start, end: closingParen.loc.end },\n messageId: 'preferWhileTrue',\n fix: canFix\n ? (fixer) => {\n return fixer.replaceTextRange(\n [forToken.range[0], closingParen.range[1]],\n 'while (true)',\n )\n }\n : undefined,\n })\n },\n }\n },\n}\n\nexport default rule\n","import type { ESLint } from 'eslint'\n\nimport { version } from '../package.json'\n\nimport noImplicitArrowLinebreak from './rules/no-implicit-arrow-linebreak'\nimport preferConstantFunction from './rules/prefer-constant-function'\nimport preferNoop from './rules/prefer-noop'\nimport preferSleep from './rules/prefer-sleep'\nimport preferWhileTrue from './rules/prefer-while-true'\n\nconst plugin = {\n meta: {\n name: 'eslint-plugin-ocavue',\n version,\n },\n rules: {\n 'no-implicit-arrow-linebreak': noImplicitArrowLinebreak,\n 'prefer-constant-function': preferConstantFunction,\n 'prefer-noop': preferNoop,\n 'prefer-sleep': preferSleep,\n 'prefer-while-true': preferWhileTrue,\n },\n} satisfies ESLint.Plugin\n\nexport default plugin\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-ocavue",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Custom ESLint rules from ocavue",
6
6
  "author": "ocavue <ocavue@gmail.com>",
7
7
  "license": "MIT",
@@ -35,14 +35,16 @@
35
35
  "eslint": "^10.0.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@ocavue/eslint-config": "^4.22.0",
38
+ "@ocavue/eslint-config": "^4.23.0",
39
39
  "@ocavue/tsconfig": "^0.7.1",
40
+ "@types/estree": "^1.0.10",
40
41
  "@types/node": "^24.0.0",
41
42
  "eslint": "^10.14.0",
42
- "knip": "^6.41.0",
43
+ "knip": "^6.44.2",
43
44
  "oxfmt": "^0.73.0",
44
45
  "tsdown": "^0.23.1",
45
46
  "typescript": "^6.0.3",
47
+ "typescript-eslint": "^8.72.1",
46
48
  "vitest": "^5.0.5"
47
49
  },
48
50
  "scripts": {
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@ import type { ESLint } from 'eslint'
3
3
  import { version } from '../package.json'
4
4
 
5
5
  import noImplicitArrowLinebreak from './rules/no-implicit-arrow-linebreak'
6
+ import preferConstantFunction from './rules/prefer-constant-function'
7
+ import preferNoop from './rules/prefer-noop'
8
+ import preferSleep from './rules/prefer-sleep'
6
9
  import preferWhileTrue from './rules/prefer-while-true'
7
10
 
8
11
  const plugin = {
@@ -12,6 +15,9 @@ const plugin = {
12
15
  },
13
16
  rules: {
14
17
  'no-implicit-arrow-linebreak': noImplicitArrowLinebreak,
18
+ 'prefer-constant-function': preferConstantFunction,
19
+ 'prefer-noop': preferNoop,
20
+ 'prefer-sleep': preferSleep,
15
21
  'prefer-while-true': preferWhileTrue,
16
22
  },
17
23
  } satisfies ESLint.Plugin
@@ -0,0 +1,134 @@
1
+ import { RuleTester } from 'eslint'
2
+ import tseslint from 'typescript-eslint'
3
+ import { test } from 'vitest'
4
+
5
+ import { useUtilsProjects } from '../utils/test-projects'
6
+
7
+ import rule from './prefer-constant-function'
8
+
9
+ const js = useUtilsProjects('js')
10
+ const ts = useUtilsProjects('ts')
11
+
12
+ function suggestion(name: string, output: string) {
13
+ return {
14
+ messageId: 'preferConstantFunction',
15
+ data: { name },
16
+ suggestions: [{ messageId: 'replaceWithConstantFunction', data: { name }, output }],
17
+ }
18
+ }
19
+
20
+ test('prefer-constant-function', () => {
21
+ const tester = new RuleTester({
22
+ languageOptions: { ecmaVersion: 'latest', sourceType: 'module' },
23
+ })
24
+ const filename = js.withUtils
25
+
26
+ tester.run('prefer-constant-function', rule, {
27
+ valid: [
28
+ // The project does not depend on `@ocavue/utils`.
29
+ { code: 'f(() => true)', filename: js.withoutUtils },
30
+ // `@ocavue/utils` itself.
31
+ { code: 'f(() => true)', filename: js.utilsRepo },
32
+
33
+ // `prefer-noop` handles empty functions.
34
+ { code: 'f(() => {})', filename },
35
+ { code: 'f(() => undefined)', filename },
36
+
37
+ // Other values.
38
+ { code: 'f(() => 0)', filename },
39
+ { code: "f(() => '')", filename },
40
+ { code: 'f(() => !0)', filename },
41
+
42
+ // Replacing would change runtime behavior.
43
+ { code: 'f(async () => true)', filename },
44
+ { code: 'f(({ a }) => false)', filename },
45
+ { code: 'const o = { get foo() { return null } }', filename },
46
+ ],
47
+ invalid: [
48
+ {
49
+ // Suggestions only: `--fix` leaves the code unchanged.
50
+ code: 'f(() => true)',
51
+ output: null,
52
+ filename,
53
+ errors: [
54
+ suggestion('returnTrue', "import { returnTrue } from '@ocavue/utils'\nf(returnTrue)"),
55
+ ],
56
+ },
57
+ {
58
+ code: 'f((_event) => { return false })',
59
+ output: null,
60
+ filename,
61
+ errors: [
62
+ suggestion('returnFalse', "import { returnFalse } from '@ocavue/utils'\nf(returnFalse)"),
63
+ ],
64
+ },
65
+ {
66
+ code: 'f(function () { return null })',
67
+ output: null,
68
+ filename,
69
+ errors: [
70
+ suggestion('returnNull', "import { returnNull } from '@ocavue/utils'\nf(returnNull)"),
71
+ ],
72
+ },
73
+ {
74
+ code: "import { noop } from '@ocavue/utils'\nf(() => true, () => null)",
75
+ output: null,
76
+ filename,
77
+ errors: [
78
+ suggestion(
79
+ 'returnTrue',
80
+ "import { noop, returnTrue } from '@ocavue/utils'\nf(returnTrue, () => null)",
81
+ ),
82
+ suggestion(
83
+ 'returnNull',
84
+ "import { noop, returnNull } from '@ocavue/utils'\nf(() => true, returnNull)",
85
+ ),
86
+ ],
87
+ },
88
+ {
89
+ // No suggestion when another binding uses the name.
90
+ code: 'const returnTrue = 1\nf(() => true)',
91
+ output: null,
92
+ filename,
93
+ errors: [{ messageId: 'preferConstantFunction', suggestions: [] }],
94
+ },
95
+ {
96
+ code: 'f(() => true)',
97
+ output: null,
98
+ filename: js.withoutUtils,
99
+ options: [{ checkDependency: false }],
100
+ errors: [
101
+ suggestion('returnTrue', "import { returnTrue } from '@ocavue/utils'\nf(returnTrue)"),
102
+ ],
103
+ },
104
+ ],
105
+ })
106
+ })
107
+
108
+ test('prefer-constant-function with TypeScript', () => {
109
+ const tester = new RuleTester({
110
+ languageOptions: {
111
+ parser: tseslint.parser,
112
+ ecmaVersion: 'latest',
113
+ sourceType: 'module',
114
+ },
115
+ })
116
+ const filename = ts.withUtils
117
+
118
+ tester.run('prefer-constant-function', rule, {
119
+ valid: [{ code: 'f(<T,>() => null)', filename }],
120
+ invalid: [
121
+ {
122
+ code: 'const isReady = (): boolean => false',
123
+ output: null,
124
+ filename,
125
+ errors: [
126
+ suggestion(
127
+ 'returnFalse',
128
+ "import { returnFalse } from '@ocavue/utils'\nconst isReady = returnFalse",
129
+ ),
130
+ ],
131
+ },
132
+ ],
133
+ })
134
+ })
@@ -0,0 +1,73 @@
1
+ import type { Rule } from 'eslint'
2
+
3
+ import {
4
+ getConstantFunctionValue,
5
+ type ConstantValue,
6
+ type FunctionNode,
7
+ } from '../utils/constant-function'
8
+ import {
9
+ createImportFix,
10
+ isPackageAvailable,
11
+ packageRuleSchema,
12
+ UTILS_PACKAGE,
13
+ } from '../utils/package-import'
14
+
15
+ const FUNCTION_NAMES: Partial<Record<ConstantValue, string>> = {
16
+ true: 'returnTrue',
17
+ false: 'returnFalse',
18
+ null: 'returnNull',
19
+ }
20
+
21
+ const rule: Rule.RuleModule = {
22
+ meta: {
23
+ type: 'suggestion',
24
+ hasSuggestions: true,
25
+ docs: {
26
+ description:
27
+ 'enforce `returnTrue`, `returnFalse` and `returnNull` from `@ocavue/utils` instead of inline functions',
28
+ url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-constant-function',
29
+ },
30
+ messages: {
31
+ preferConstantFunction: 'Use `{{name}}` from `@ocavue/utils` instead of an inline function.',
32
+ replaceWithConstantFunction: 'Replace with `{{name}}` from `@ocavue/utils`.',
33
+ },
34
+ schema: packageRuleSchema,
35
+ },
36
+ create(context) {
37
+ if (!isPackageAvailable(context, UTILS_PACKAGE)) {
38
+ return {}
39
+ }
40
+
41
+ function check(node: FunctionNode): void {
42
+ const value = getConstantFunctionValue(node)
43
+ const name = value && FUNCTION_NAMES[value]
44
+ if (!name) {
45
+ return
46
+ }
47
+
48
+ // `returnFalse` returns the literal type `false`, while `() => false`
49
+ // returns `boolean`. The swap can narrow inferred types, for example
50
+ // `useState(() => false)`, so it is a suggestion and not an autofix.
51
+ const fix = createImportFix({
52
+ context,
53
+ node,
54
+ name,
55
+ packageName: UTILS_PACKAGE,
56
+ replace: (local) => local,
57
+ })
58
+ context.report({
59
+ node,
60
+ messageId: 'preferConstantFunction',
61
+ data: { name },
62
+ suggest: fix ? [{ messageId: 'replaceWithConstantFunction', data: { name }, fix }] : [],
63
+ })
64
+ }
65
+
66
+ return {
67
+ ArrowFunctionExpression: check,
68
+ FunctionExpression: check,
69
+ }
70
+ },
71
+ }
72
+
73
+ export default rule
@@ -0,0 +1,263 @@
1
+ import { RuleTester } from 'eslint'
2
+ import tseslint from 'typescript-eslint'
3
+ import { test } from 'vitest'
4
+
5
+ import { useUtilsProjects } from '../utils/test-projects'
6
+
7
+ import rule from './prefer-noop'
8
+
9
+ const js = useUtilsProjects('jsx')
10
+ const ts = useUtilsProjects('ts')
11
+
12
+ const IMPORT = "import { noop } from '@ocavue/utils'\n"
13
+
14
+ test('prefer-noop', () => {
15
+ const tester = new RuleTester({
16
+ languageOptions: {
17
+ ecmaVersion: 'latest',
18
+ sourceType: 'module',
19
+ parserOptions: { ecmaFeatures: { jsx: true } },
20
+ },
21
+ })
22
+ const filename = js.withUtils
23
+
24
+ tester.run('prefer-noop', rule, {
25
+ valid: [
26
+ // The project does not depend on `@ocavue/utils`.
27
+ { code: 'f(() => {})', filename: js.withoutUtils },
28
+ // `@ocavue/utils` itself.
29
+ { code: 'f(() => {})', filename: js.utilsRepo },
30
+ // No file on disk.
31
+ { code: 'f(() => {})' },
32
+
33
+ // Not an empty function.
34
+ { code: 'f(() => true)', filename },
35
+ { code: 'f(() => null)', filename },
36
+ { code: 'f(() => 1)', filename },
37
+ { code: 'f(() => x)', filename },
38
+ { code: 'f(() => { g() })', filename },
39
+ { code: 'f(() => { g(); return })', filename },
40
+
41
+ // Replacing would change runtime behavior.
42
+ { code: 'f(async () => {})', filename },
43
+ { code: 'f(function* () {})', filename },
44
+ { code: 'f(({ a }) => {})', filename },
45
+ { code: 'f(([a]) => {})', filename },
46
+ { code: 'f((x = g()) => {})', filename },
47
+ { code: 'f(function named() {})', filename },
48
+ { code: 'new (function () {})()', filename },
49
+
50
+ // Methods.
51
+ { code: 'const o = { foo() {} }', filename },
52
+ { code: 'const o = { set foo(v) {} }', filename },
53
+ { code: 'class A { foo() {} }', filename },
54
+ { code: 'class A { constructor() {} }', filename },
55
+
56
+ // Already uses `noop`.
57
+ { code: `${IMPORT}f(noop)`, filename },
58
+ ],
59
+ invalid: [
60
+ {
61
+ code: 'f(() => {})',
62
+ output: `${IMPORT}f(noop)`,
63
+ filename,
64
+ errors: [{ messageId: 'preferNoop' }],
65
+ },
66
+ {
67
+ code: 'const el = <button onClick={() => {}} />',
68
+ output: `${IMPORT}const el = <button onClick={noop} />`,
69
+ filename,
70
+ errors: [{ messageId: 'preferNoop' }],
71
+ },
72
+ {
73
+ code: 'f(function () {})',
74
+ output: `${IMPORT}f(noop)`,
75
+ filename,
76
+ errors: [{ messageId: 'preferNoop' }],
77
+ },
78
+ {
79
+ code: 'f(() => undefined)',
80
+ output: `${IMPORT}f(noop)`,
81
+ filename,
82
+ errors: [{ messageId: 'preferNoop' }],
83
+ },
84
+ {
85
+ code: 'f(() => void 0)',
86
+ output: `${IMPORT}f(noop)`,
87
+ filename,
88
+ errors: [{ messageId: 'preferNoop' }],
89
+ },
90
+ {
91
+ code: 'f(() => { return })',
92
+ output: `${IMPORT}f(noop)`,
93
+ filename,
94
+ errors: [{ messageId: 'preferNoop' }],
95
+ },
96
+ {
97
+ code: 'f(() => { return undefined })',
98
+ output: `${IMPORT}f(noop)`,
99
+ filename,
100
+ errors: [{ messageId: 'preferNoop' }],
101
+ },
102
+ {
103
+ code: 'f((_a, ...rest) => {})',
104
+ output: `${IMPORT}f(noop)`,
105
+ filename,
106
+ errors: [{ messageId: 'preferNoop' }],
107
+ },
108
+ {
109
+ code: 'const o = { foo: () => {} }',
110
+ output: `${IMPORT}const o = { foo: noop }`,
111
+ filename,
112
+ errors: [{ messageId: 'preferNoop' }],
113
+ },
114
+ {
115
+ code: 'class A { foo = () => {} }',
116
+ output: `${IMPORT}class A { foo = noop }`,
117
+ filename,
118
+ errors: [{ messageId: 'preferNoop' }],
119
+ },
120
+ {
121
+ code: 'function f(cb = () => {}) {}',
122
+ output: `${IMPORT}function f(cb = noop) {}`,
123
+ filename,
124
+ errors: [{ messageId: 'preferNoop' }],
125
+ },
126
+
127
+ // Import handling.
128
+ {
129
+ code: "import { once } from '@ocavue/utils'\nf(() => {})",
130
+ output: "import { once, noop } from '@ocavue/utils'\nf(noop)",
131
+ filename,
132
+ errors: [{ messageId: 'preferNoop' }],
133
+ },
134
+ {
135
+ code: "import { noop as n } from '@ocavue/utils'\nf(() => {})",
136
+ output: "import { noop as n } from '@ocavue/utils'\nf(n)",
137
+ filename,
138
+ errors: [{ messageId: 'preferNoop' }],
139
+ },
140
+ {
141
+ code: "import a from 'a';\nf(() => {})",
142
+ output: "import a from 'a';\nimport { noop } from '@ocavue/utils';\nf(noop)",
143
+ filename,
144
+ errors: [{ messageId: 'preferNoop' }],
145
+ },
146
+ {
147
+ code: "'use client'\nf(() => {})",
148
+ output: `'use client'\n${IMPORT}f(noop)`,
149
+ filename,
150
+ errors: [{ messageId: 'preferNoop' }],
151
+ },
152
+ {
153
+ // The file imports `@ocavue/utils`, so the rule is on even without
154
+ // the dependency.
155
+ code: "import { once } from '@ocavue/utils'\nf(() => {})",
156
+ output: "import { once, noop } from '@ocavue/utils'\nf(noop)",
157
+ filename: js.withoutUtils,
158
+ errors: [{ messageId: 'preferNoop' }],
159
+ },
160
+ {
161
+ // Only one fix is applied per pass because each fix adds the import.
162
+ code: 'f(() => {})\ng(() => {})',
163
+ output: `${IMPORT}f(noop)\ng(() => {})`,
164
+ filename,
165
+ errors: [{ messageId: 'preferNoop' }, { messageId: 'preferNoop' }],
166
+ },
167
+ {
168
+ code: `${IMPORT}f(() => {})\ng(() => {})`,
169
+ output: `${IMPORT}f(noop)\ng(noop)`,
170
+ filename,
171
+ errors: [{ messageId: 'preferNoop' }, { messageId: 'preferNoop' }],
172
+ },
173
+
174
+ // Reported without a fix.
175
+ {
176
+ code: 'const noop = 1\nf(() => {})',
177
+ output: null,
178
+ filename,
179
+ errors: [{ messageId: 'preferNoop' }],
180
+ },
181
+ {
182
+ code: `${IMPORT}function g(noop) { f(() => {}) }`,
183
+ output: null,
184
+ filename,
185
+ errors: [{ messageId: 'preferNoop' }],
186
+ },
187
+ {
188
+ code: 'noop()\nf(() => {})',
189
+ output: null,
190
+ filename,
191
+ errors: [{ messageId: 'preferNoop' }],
192
+ },
193
+ {
194
+ code: 'f(() => { /* intentionally empty */ })',
195
+ output: null,
196
+ filename,
197
+ errors: [{ messageId: 'preferNoop' }],
198
+ },
199
+ {
200
+ code: 'f(() => {})',
201
+ output: null,
202
+ filename,
203
+ languageOptions: { sourceType: 'commonjs' },
204
+ errors: [{ messageId: 'preferNoop' }],
205
+ },
206
+
207
+ // `checkDependency: false` reports in every project.
208
+ {
209
+ code: 'f(() => {})',
210
+ output: `${IMPORT}f(noop)`,
211
+ filename: js.withoutUtils,
212
+ options: [{ checkDependency: false }],
213
+ errors: [{ messageId: 'preferNoop' }],
214
+ },
215
+ ],
216
+ })
217
+ })
218
+
219
+ test('prefer-noop with TypeScript', () => {
220
+ const tester = new RuleTester({
221
+ languageOptions: {
222
+ parser: tseslint.parser,
223
+ ecmaVersion: 'latest',
224
+ sourceType: 'module',
225
+ },
226
+ })
227
+ const filename = ts.withUtils
228
+
229
+ tester.run('prefer-noop', rule, {
230
+ valid: [
231
+ { code: 'f(<T,>() => {})', filename },
232
+ { code: 'class A { foo(): void {} }', filename },
233
+ { code: 'abstract class A { abstract foo(): void }', filename },
234
+ ],
235
+ invalid: [
236
+ {
237
+ code: 'const f: () => void = (): void => {}',
238
+ output: `${IMPORT}const f: () => void = noop`,
239
+ filename,
240
+ errors: [{ messageId: 'preferNoop' }],
241
+ },
242
+ {
243
+ code: 'f((event: Event) => {})',
244
+ output: `${IMPORT}f(noop)`,
245
+ filename,
246
+ errors: [{ messageId: 'preferNoop' }],
247
+ },
248
+ {
249
+ code: "'use client'\nf(() => {})",
250
+ output: `'use client'\n${IMPORT}f(noop)`,
251
+ filename,
252
+ errors: [{ messageId: 'preferNoop' }],
253
+ },
254
+ {
255
+ // A type-only import is not reused.
256
+ code: "import type { Counter } from '@ocavue/utils'\nf(() => {})",
257
+ output: `import type { Counter } from '@ocavue/utils'\n${IMPORT}f(noop)`,
258
+ filename,
259
+ errors: [{ messageId: 'preferNoop' }],
260
+ },
261
+ ],
262
+ })
263
+ })
@@ -0,0 +1,53 @@
1
+ import type { Rule } from 'eslint'
2
+
3
+ import { getConstantFunctionValue, type FunctionNode } from '../utils/constant-function'
4
+ import {
5
+ createImportFix,
6
+ isPackageAvailable,
7
+ packageRuleSchema,
8
+ UTILS_PACKAGE,
9
+ } from '../utils/package-import'
10
+
11
+ const rule: Rule.RuleModule = {
12
+ meta: {
13
+ type: 'suggestion',
14
+ fixable: 'code',
15
+ docs: {
16
+ description: 'enforce `noop` from `@ocavue/utils` instead of inline empty functions',
17
+ url: 'https://github.com/ocavue/eslint-plugin-ocavue#prefer-noop',
18
+ },
19
+ messages: {
20
+ preferNoop: 'Use `noop` from `@ocavue/utils` instead of an inline empty function.',
21
+ },
22
+ schema: packageRuleSchema,
23
+ },
24
+ create(context) {
25
+ if (!isPackageAvailable(context, UTILS_PACKAGE)) {
26
+ return {}
27
+ }
28
+
29
+ function check(node: FunctionNode): void {
30
+ if (getConstantFunctionValue(node) !== 'undefined') {
31
+ return
32
+ }
33
+ context.report({
34
+ node,
35
+ messageId: 'preferNoop',
36
+ fix: createImportFix({
37
+ context,
38
+ node,
39
+ name: 'noop',
40
+ packageName: UTILS_PACKAGE,
41
+ replace: (local) => local,
42
+ }),
43
+ })
44
+ }
45
+
46
+ return {
47
+ ArrowFunctionExpression: check,
48
+ FunctionExpression: check,
49
+ }
50
+ },
51
+ }
52
+
53
+ export default rule