Files
gridpilot.gg/apps/website/eslint-rules/filename-matches-export.js
2026-01-13 00:16:14 +01:00

95 lines
2.7 KiB
JavaScript

/**
* ESLint rule to enforce filename matches exported name
*
* The filename (without extension) should match the exported name exactly.
* For example: AdminDashboardPageQuery.ts should export AdminDashboardPageQuery
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Enforce filename matches exported name',
category: 'Best Practices',
recommended: true,
},
fixable: null,
schema: [],
messages: {
filenameMismatch: 'Filename "{{filename}}" should match exported name "{{exportName}}"',
noMatchFound: 'No export found that matches filename "{{filename}}"',
},
},
create(context) {
const filename = context.getFilename();
// Extract base filename without extension
const baseName = filename.split('/').pop(); // Get last part of path
const nameWithoutExt = baseName?.replace(/\.(ts|tsx|js|jsx)$/, '');
// Skip test files, index files, and type definition files
if (!nameWithoutExt ||
nameWithoutExt.endsWith('.test') ||
nameWithoutExt.endsWith('.spec') ||
nameWithoutExt === 'index' ||
nameWithoutExt.endsWith('.d')) {
return {};
}
let exportedName = null;
return {
// Track named exports
ExportNamedDeclaration(node) {
if (node.declaration) {
if (node.declaration.id) {
exportedName = node.declaration.id.name;
} else if (node.declaration.declarations) {
// Multiple const exports - use the first one
const first = node.declaration.declarations[0];
if (first.id && first.id.name) {
exportedName = first.id.name;
}
}
} else if (node.specifiers && node.specifiers.length > 0) {
// Re-exports - use the first one
exportedName = node.specifiers[0].exported.name;
}
},
// Track default exports
ExportDefaultDeclaration(node) {
if (node.declaration && node.declaration.id) {
exportedName = node.declaration.id.name;
} else {
exportedName = 'default';
}
},
'Program:exit'() {
if (!exportedName) {
// No export found - this might be okay for some files
return;
}
if (exportedName === 'default') {
// Default exports don't need to match filename
return;
}
if (exportedName !== nameWithoutExt) {
context.report({
node: context.getSourceCode().ast,
messageId: 'filenameMismatch',
data: {
filename: nameWithoutExt,
exportName: exportedName,
},
});
}
},
};
},
};