website refactor
This commit is contained in:
@@ -1,22 +1,23 @@
|
||||
/**
|
||||
* ESLint rule to enforce UI element purity
|
||||
* ESLint rule to enforce UI element purity and architectural boundaries
|
||||
*
|
||||
* UI elements in ui/ must be:
|
||||
* - Stateless (no useState, useReducer)
|
||||
* - No side effects (no useEffect)
|
||||
* - Pure functions that only render based on props
|
||||
* - Isolated (cannot import from outside the ui/ directory)
|
||||
*
|
||||
* Rationale:
|
||||
* - ui/ is for reusable, pure presentation elements
|
||||
* - Stateful logic belongs in components/ or hooks/
|
||||
* - This ensures maximum reusability and testability
|
||||
* - Isolation ensures UI elements don't depend on app-specific logic
|
||||
* - Raw HTML and className are allowed in ui/ for implementation
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Enforce UI elements are pure and stateless',
|
||||
description: 'Enforce UI elements are pure and isolated',
|
||||
category: 'Architecture',
|
||||
recommended: true,
|
||||
},
|
||||
@@ -26,6 +27,7 @@ module.exports = {
|
||||
noStateInUi: 'UI elements in ui/ must be stateless. Use components/ for stateful wrappers.',
|
||||
noHooksInUi: 'UI elements must not use hooks. Use components/ or hooks/ for stateful logic.',
|
||||
noSideEffects: 'UI elements must not have side effects. Use components/ for side effect logic.',
|
||||
noExternalImports: 'UI elements in ui/ cannot import from outside the ui/ directory. Only npm packages and other UI elements are allowed.',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,6 +38,38 @@ module.exports = {
|
||||
if (!isInUi) return {};
|
||||
|
||||
return {
|
||||
// Check for imports from outside ui/
|
||||
ImportDeclaration(node) {
|
||||
const importPath = node.source.value;
|
||||
|
||||
// Allow npm packages (don't start with . or @/)
|
||||
if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check internal imports
|
||||
const isInternalUiImport = importPath.startsWith('@/ui/') ||
|
||||
(importPath.startsWith('.') && !importPath.includes('..'));
|
||||
|
||||
// Special case for relative imports that stay within ui/
|
||||
let staysInUi = false;
|
||||
if (importPath.startsWith('.')) {
|
||||
const path = require('path');
|
||||
const absoluteImportPath = path.resolve(path.dirname(filename), importPath);
|
||||
const uiDir = filename.split('/ui/')[0] + '/ui';
|
||||
if (absoluteImportPath.startsWith(uiDir)) {
|
||||
staysInUi = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInternalUiImport && !staysInUi) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noExternalImports',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Check for 'use client' directive
|
||||
ExpressionStatement(node) {
|
||||
if (node.expression.type === 'Literal' &&
|
||||
@@ -77,31 +111,6 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Check for class components with state
|
||||
ClassDeclaration(node) {
|
||||
if (node.superClass &&
|
||||
node.superClass.type === 'Identifier' &&
|
||||
node.superClass.name === 'Component') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noStateInUi',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Check for direct state assignment (rare but possible)
|
||||
AssignmentExpression(node) {
|
||||
if (node.left.type === 'MemberExpression' &&
|
||||
node.left.property.type === 'Identifier' &&
|
||||
(node.left.property.name === 'state' ||
|
||||
node.left.property.name === 'setState')) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noStateInUi',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user