chore: add no-imports-from-self eslint rule to monorepo (#7272)

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
This commit is contained in:
Alessio Gravili
2025-03-03 08:52:24 -07:00
committed by GitHub
parent 35ad5d7546
commit 6d7b670997
6 changed files with 133 additions and 3 deletions

View File

@ -114,6 +114,12 @@ module.exports = {
'lexical/no-optional-chaining': OFF,
},
},
{
files: ['packages/**/__tests__/**'],
rules: {
'lexical/no-imports-from-self': OFF,
},
},
{
files: [
'packages/*/src/index.ts',
@ -169,7 +175,6 @@ module.exports = {
// and then enable some React specific ones.
rules: {
'accessor-pairs': OFF,
'consistent-return': OFF,
curly: [ERROR, 'all'],
'dot-location': [ERROR, 'property'],
@ -200,6 +205,8 @@ module.exports = {
'keyword-spacing': [ERROR, {after: true, before: true}],
'lexical/no-imports-from-self': ERROR,
// Enforced by Prettier
// TODO: Prettier doesn't handle long strings or long comments. Not a big
// deal. But I turned it off because loading the plugin causes some obscure

View File

@ -14,11 +14,13 @@ module.exports = {
configs: {
all: {
rules: {
'lexical/no-imports-from-self': 'error',
'lexical/no-optional-chaining': 'error',
},
},
recommended: {
rules: {
'lexical/no-imports-from-self': 'error',
'lexical/no-optional-chaining': 'error',
},
},

View File

@ -9,7 +9,9 @@
'use strict';
const noOptionalChaining = require('./no-optional-chaining');
const noImportsFromSelf = require('./no-imports-from-self');
module.exports = {
'no-imports-from-self': noImportsFromSelf,
'no-optional-chaining': noOptionalChaining,
};

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
// From: https://github.com/payloadcms/payload/blob/main/packages/eslint-plugin/customRules/no-imports-from-self.js
'use strict';
const fs = require('fs');
const path = require('node:path');
const {PackageMetadata} = require('../../../scripts/shared/PackageMetadata.js');
const packageMetadataCache = new Map();
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create(context) {
let packageMetadata = null;
return {
ImportDeclaration(node) {
// Skip type-only imports
if (node.importKind === 'type' || node.importKind === 'typeof') {
return;
}
const importPath = node.source.value;
if (!packageMetadata) {
packageMetadata = getNearestPackageJsonMetadata(
path.dirname(context.filename),
);
}
// Skip if package is private
if (!packageMetadata || packageMetadata.isPrivate()) {
return;
}
// Self-import
if (importPath === packageMetadata.packageJson.name) {
context.report({
message: `Package "${packageMetadata.packageJson.name}" should not import from itself. Use relative instead.`,
node,
});
// Import from subpath export => only invalid if subpath export resolves to current file
} else if (importPath.startsWith(packageMetadata.packageJson.name)) {
// Check if this file is referenced in the package.json exports entry for the import path
const exports = packageMetadata.getNormalizedNpmModuleExportEntries();
const exportEntry = exports.find(([name]) => importPath === name);
if (exportEntry) {
const resolvedExport = exportEntry[1].import.default;
// Resolved path of file that is trying to be imported, without extensions
const resolvedExportPath = path.join(
path.dirname(packageMetadata.packageJsonPath),
'src',
resolvedExport,
);
const baseResolvedExportPath = resolvedExportPath.substring(
0,
resolvedExportPath.lastIndexOf('.'),
);
const baseCurFilePath = context.filename.substring(
0,
context.filename.lastIndexOf('.'),
);
if (baseCurFilePath === baseResolvedExportPath) {
context.report({
message: `Package "${resolvedExport}" should not import from itself. Use relative instead.`,
node,
});
}
}
}
},
};
},
meta: {
docs: {
category: 'Best Practices',
description:
'Disallow a package from importing from itself, except type-only imports',
recommended: true,
},
schema: [],
},
};
/**
* @param {string} startDir
*/
function getNearestPackageJsonMetadata(startDir) {
let currentDir = startDir;
while (currentDir !== path.dirname(currentDir)) {
// Root directory check
const pkgPath = path.join(currentDir, 'package.json');
if (fs.existsSync(pkgPath)) {
if (packageMetadataCache.has(pkgPath)) {
return packageMetadataCache.get(pkgPath);
}
const packageMetadata = new PackageMetadata(pkgPath);
packageMetadataCache.set(pkgPath, packageMetadata);
return packageMetadata;
}
currentDir = path.dirname(currentDir);
}
return null;
}

View File

@ -8,8 +8,8 @@
import type {KlassConstructor, LexicalEditor} from '../LexicalEditor';
import type {ElementNode} from './LexicalElementNode';
import type {EditorConfig} from 'lexical';
import {EditorConfig} from 'lexical';
import invariant from 'shared/invariant';
import {LexicalNode} from '../LexicalNode';

View File

@ -1,6 +1,6 @@
{
"name": "shared",
"private": "true",
"private": true,
"keywords": [
"react",
"lexical",