From 9b27f03e61aa32c7cd5cc78a20646a2f7aa4e78b Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Thu, 15 Aug 2024 09:02:29 -0400 Subject: [PATCH] feat(eslint): no-imports-from-self rule (#7691) New rule to prevent a package from importing from itself. --- eslint.config.js | 1 + .../customRules/no-imports-from-self.js | 67 +++++++++++++++++++ packages/eslint-plugin/index.mjs | 2 + 3 files changed, 70 insertions(+) create mode 100644 packages/eslint-plugin/customRules/no-imports-from-self.js diff --git a/eslint.config.js b/eslint.config.js index a5fac2e30..3dbd4595e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -62,6 +62,7 @@ export const rootEslintConfig = [ 'payload/no-jsx-import-statements': 'warn', 'payload/no-relative-monorepo-imports': 'error', 'payload/no-imports-from-exports-dir': 'error', + 'payload/no-imports-from-self': 'error', }, }, { diff --git a/packages/eslint-plugin/customRules/no-imports-from-self.js b/packages/eslint-plugin/customRules/no-imports-from-self.js new file mode 100644 index 000000000..d8510e0f1 --- /dev/null +++ b/packages/eslint-plugin/customRules/no-imports-from-self.js @@ -0,0 +1,67 @@ +import fs from 'fs' +import path from 'path' + +/** @type {import('eslint').Rule.RuleModule} */ +export const rule = { + meta: { + docs: { + description: 'Disallow a package from importing from itself', + category: 'Best Practices', + recommended: true, + }, + fixable: 'code', + schema: [], + }, + + create(context) { + let packageName = null + + return { + ImportDeclaration(node) { + const importPath = node.source.value + const pkgName = getPackageName(context, packageName) + if (pkgName && importPath.startsWith(pkgName)) { + context.report({ + node, + message: `Package "${pkgName}" should not import from itself. Use relative instead.`, + }) + } + }, + } + }, +} + +export default rule + +/** + * @param {import('eslint').Rule.RuleContext} context + * @param {string|undefined} packageName + */ +function getPackageName(context, packageName) { + if (packageName) { + return packageName + } + + const fileName = context.getFilename() + const pkg = findNearestPackageJson(path.dirname(fileName)) + if (pkg) { + return pkg.name + } +} + +/** + * @param {string} startDir + */ +function findNearestPackageJson(startDir) { + let currentDir = startDir + while (currentDir !== path.dirname(currentDir)) { + // Root directory check + const pkgPath = path.join(currentDir, 'package.json') + if (fs.existsSync(pkgPath)) { + const pkgContent = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) + return pkgContent + } + currentDir = path.dirname(currentDir) + } + return null +} diff --git a/packages/eslint-plugin/index.mjs b/packages/eslint-plugin/index.mjs index 1f54e274c..dd743b077 100644 --- a/packages/eslint-plugin/index.mjs +++ b/packages/eslint-plugin/index.mjs @@ -3,6 +3,7 @@ import noNonRetryableAssertions from './customRules/no-non-retryable-assertions. import noRelativeMonorepoImports from './customRules/no-relative-monorepo-imports.js' import noImportsFromExportsDir from './customRules/no-imports-from-exports-dir.js' import noFlakyAssertions from './customRules/no-flaky-assertions.js' +import noImportsFromSelf from './customRules/no-imports-from-self.js' /** * @type {import('eslint').ESLint.Plugin} @@ -13,6 +14,7 @@ const index = { 'no-non-retryable-assertions': noNonRetryableAssertions, 'no-relative-monorepo-imports': noRelativeMonorepoImports, 'no-imports-from-exports-dir': noImportsFromExportsDir, + 'no-imports-from-self': noImportsFromSelf, 'no-flaky-assertions': noFlakyAssertions, 'no-wait-function': { create: function (context) {