Files
gridpilot.gg/apps/website/eslint-rules/lib-no-next-imports.js
2026-01-13 00:16:14 +01:00

109 lines
3.4 KiB
JavaScript

/**
* ESLint rule to forbid Next.js imports in lib/ directory
*
* The lib/ directory should be framework-agnostic.
* Next.js imports (redirect, cookies, headers, etc.) should only be in app/ directory.
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Forbid Next.js imports in lib/ directory',
category: 'Architecture',
recommended: true,
},
fixable: null,
schema: [],
messages: {
noNextImports: 'Next.js imports are forbidden in lib/ directory. Found: {{import}} from "{{source}}"',
noNextRedirect: 'redirect() must be used in app/ directory, not lib/ directory',
noNextCookies: 'cookies() must be used in app/ directory, not lib/ directory',
noNextHeaders: 'headers() must be used in app/ directory, not lib/ directory',
},
},
create(context) {
const filename = context.getFilename();
const isInLib = filename.includes('/lib/');
// Skip if not in lib directory
if (!isInLib) return {};
return {
// Track import statements
ImportDeclaration(node) {
const source = node.source.value;
// Check for Next.js imports
if (source === 'next/navigation' ||
source === 'next/headers' ||
source === 'next/cookies' ||
source === 'next/router' ||
source === 'next/link' ||
source === 'next/image' ||
source === 'next/script' ||
source === 'next/dynamic') {
// Check for specific named imports
node.specifiers.forEach(spec => {
if (spec.type === 'ImportSpecifier') {
const imported = spec.imported.name;
if (imported === 'redirect') {
context.report({
node: spec,
messageId: 'noNextRedirect',
});
} else if (imported === 'cookies') {
context.report({
node: spec,
messageId: 'noNextCookies',
});
} else if (imported === 'headers') {
context.report({
node: spec,
messageId: 'noNextHeaders',
});
} else {
context.report({
node: spec,
messageId: 'noNextImports',
data: { import: imported, source },
});
}
} else if (spec.type === 'ImportDefaultSpecifier') {
context.report({
node: spec,
messageId: 'noNextImports',
data: { import: 'default', source },
});
}
});
}
},
// Also check for require() calls
CallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'require') {
if (node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
const source = node.arguments[0].value;
if (source === 'next/navigation' ||
source === 'next/headers' ||
source === 'next/cookies' ||
source === 'next/router') {
context.report({
node,
messageId: 'noNextImports',
data: { import: 'require()', source },
});
}
}
}
},
};
},
};