eslint-plugin-filename-export
Version:
ESLint plugin that ensures non-index filenames match to at least one named export.
163 lines • 6.92 kB
JavaScript
"use strict";
const utils_1 = require("@typescript-eslint/utils");
const node_path_1 = require("node:path");
const createRule = utils_1.ESLintUtils.RuleCreator(() => 'https://github.com/ekwoka/eslint-plugin-filename-export');
const getName = (exported) => {
if ('name' in exported)
return exported.name;
if ('id' in exported && exported.id)
if ('name' in exported.id)
return exported.id.name;
else if ('value' in exported.id)
return exported.id.value;
return null;
};
const getExportedNames = (exported) => {
if (exported.declaration)
return getNamesFromDeclarations(exported.declaration);
if (exported.specifiers)
return getNamesFromSpecifiers(exported.specifiers);
return [];
};
const getNamesFromDeclarations = (declaration) => {
var _a;
if ('declarations' in declaration && declaration.declarations)
return declaration.declarations.map((declaration) => { var _a; return (_a = getName(declaration)) !== null && _a !== void 0 ? _a : ''; });
return [(_a = getName(declaration)) !== null && _a !== void 0 ? _a : ''];
};
const getNamesFromSpecifiers = (specifiers) => specifiers.map((specifier) => specifier.exported.name);
const compare = (names, transformers) => {
const [name, filename] = names.map((string) => transformers.reduce((acc, fn) => fn(acc), string));
return name === filename;
};
const makeTransformers = (options) => {
const transformers = [];
if (options.stripextra)
transformers.push((name) => name.replace(/[^a-zA-Z0-9]/g, ''));
if (options.casing !== 'strict')
transformers.push((name) => name.toLowerCase());
return transformers;
};
module.exports = {
rules: {
'match-named-export': createRule({
name: 'match-named-export',
defaultOptions: [
{
stripextra: false,
casing: 'loose',
},
],
meta: {
docs: {
description: 'Enforce filename matches named export',
recommended: 'error',
},
messages: {
noMatchingExport: 'Filename does not match any named exports',
},
type: 'suggestion',
schema: [
{
type: 'object',
properties: {
stripextra: {
type: 'boolean',
},
casing: {
enum: ['strict', 'loose'],
},
},
},
],
},
create(context) {
var _a;
const transformers = makeTransformers((_a = context.options[0]) !== null && _a !== void 0 ? _a : {});
return {
Program(node) {
const filename = context.getFilename();
const filenameSansExt = (0, node_path_1.basename)(filename, (0, node_path_1.extname)(filename));
if (['index', 'types'].includes(filenameSansExt) ||
/\.(test|spec|stories)$/.test(filenameSansExt))
return;
if (node.body.find((item) => item.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration))
return;
const namedExports = node.body.filter((item) => item.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration);
if (!namedExports.length)
return;
const matchingExport = namedExports
.flatMap((exp) => getExportedNames(exp))
.some((name) => compare([name, filenameSansExt], transformers));
if (!matchingExport)
context.report({
messageId: 'noMatchingExport',
node,
});
},
};
},
}),
'match-default-export': createRule({
name: 'match-default-export',
defaultOptions: [
{
stripextra: false,
casing: 'loose',
},
],
meta: {
docs: {
description: 'Enforce filename matches named export',
recommended: 'error',
},
messages: {
defaultExportDoesNotMatch: 'Filename does not match the default export',
},
type: 'suggestion',
schema: [
{
type: 'object',
properties: {
stripextra: {
type: 'boolean',
},
casing: {
enum: ['strict', 'loose'],
},
},
},
],
},
create(context) {
var _a;
const transformers = makeTransformers((_a = context.options[0]) !== null && _a !== void 0 ? _a : {});
return {
Program(node) {
const filename = context.getFilename();
const filenameSansExt = (0, node_path_1.basename)(filename, (0, node_path_1.extname)(filename));
if (['index', 'types'].includes(filenameSansExt) ||
/\.(test|spec|stories)$/.test(filenameSansExt))
return;
const defaultExport = node.body.find((item) => item.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration);
if (!defaultExport)
return;
const declaration = defaultExport.declaration;
if (!('name' in declaration || 'id' in declaration))
return;
const defaultName = getName(declaration);
if (!defaultName)
return;
const isMatching = compare([defaultName, filenameSansExt], transformers);
if (!isMatching)
context.report({
messageId: 'defaultExportDoesNotMatch',
node,
});
},
};
},
}),
},
};
//# sourceMappingURL=index.js.map