diff --git a/eslint/babel-eslint-plugin-development/src/rules/plugin-name.js b/eslint/babel-eslint-plugin-development/src/rules/plugin-name.js new file mode 100644 index 0000000000..58a4bc9f63 --- /dev/null +++ b/eslint/babel-eslint-plugin-development/src/rules/plugin-name.js @@ -0,0 +1,52 @@ +"use strict"; + +const isBabelPluginFactory = require("../utils/is-babel-plugin-factory"); + +function getReturnValue(node) { + const { body } = node; + + if (body.type === "BlockStatement") { + const returnNode = body.body.find(n => n.type === "ReturnStatement"); + return returnNode && returnNode.argument; + } + + // Arrow functions with implicit return + return body; +} + +module.exports = { + meta: { + schema: [], + }, + create(context /*: Context */) { + let pluginFound = false; + + return { + FunctionDeclaration: functionVisitor, + FunctionExpression: functionVisitor, + ArrowFunctionExpression: functionVisitor, + + "Program:exit"(node) { + if (!pluginFound) { + context.report(node, "This file does not export a Babel plugin."); + } + }, + }; + + function functionVisitor(node) { + if (!isBabelPluginFactory(node, context.getScope())) return; + + const returnValue = getReturnValue(node); + if (!returnValue || returnValue.type !== "ObjectExpression") return; + + pluginFound = true; + + if (!returnValue.properties.some(p => p.key.name === "name")) { + context.report( + returnValue, + "This Babel plugin doesn't have a 'name' property.", + ); + } + } + }, +}; diff --git a/eslint/babel-eslint-plugin-development/tests/rules/plugin-name.js b/eslint/babel-eslint-plugin-development/tests/rules/plugin-name.js new file mode 100644 index 0000000000..670ced5122 --- /dev/null +++ b/eslint/babel-eslint-plugin-development/tests/rules/plugin-name.js @@ -0,0 +1,54 @@ +"use strict"; + +const rule = require("../../src/rules/plugin-name"); +const { RuleTester } = require("eslint"); + +const missingPluginError = "This file does not export a Babel plugin."; +const missingNameError = "This Babel plugin doesn't have a 'name' property."; + +const ruleTester = new RuleTester({ + parserOptions: { sourceType: "module" }, +}); + +ruleTester.run("plugin-name", rule, { + valid: [ + `export default function () { return { name: "test-plugin" } }`, + `import { declare } from "@babel/helper-plugin-utils"; declare(() => { return { name: "test-plugin" } })`, + `import { declare } from "@babel/helper-plugin-utils"; declare(() => ({ name: "test-plugin" }))`, + `module.exports = function () { return { name: "foo" }; }`, + ], + invalid: [ + { + code: `function fn() { return { name: "foo" } }`, + errors: [missingPluginError], + }, + { + code: `export function fn() { return { name: "foo" } }`, + errors: [missingPluginError], + }, + { + code: `(function fn() { return { name: "foo" } })`, + errors: [missingPluginError], + }, + { + code: `() => { return { name: "foo" } }`, + errors: [missingPluginError], + }, + { + code: `export default function fn() {}`, + errors: [missingPluginError], + }, + { + code: `export default function fn() { return {} }`, + errors: [missingNameError], + }, + { + code: `import { declare } from "@babel/helper-plugin-utils"; declare(() => ({}))`, + errors: [missingNameError], + }, + { + code: `module.exports = function () { return {} }`, + errors: [missingNameError], + }, + ], +});