import ExportMap, { recursivePatternCapture } from '../ExportMap';
import docsUrl from '../docsUrl';
import includes from 'array-includes';
const rootProgram = 'root';
const tsTypePrefix = 'type:';
function isTypescriptFunctionOverloads(nodes) {
const types = new Set(Array.from(nodes, node => node.parent.type));
return (
types.has('TSDeclareFunction') &&
(
types.size === 1 ||
(types.size === 2 && types.has('FunctionDeclaration'))
)
);
}
module.exports = {
meta: {
type: 'problem',
docs: {
url: docsUrl('export'),
},
schema: [],
},
create(context) {
const namespace = new Map([[rootProgram, new Map()]]);
function addNamed(name, node, parent, isType) {
if (!namespace.has(parent)) {
namespace.set(parent, new Map());
}
const named = namespace.get(parent);
const key = isType ? `${tsTypePrefix}${name}` : name;
let nodes = named.get(key);
if (nodes == null) {
nodes = new Set();
named.set(key, nodes);
}
nodes.add(node);
}
function getParent(node) {
if (node.parent && node.parent.type === 'TSModuleBlock') {
return node.parent.parent;
}
return rootProgram;
}
return {
'ExportDefaultDeclaration': (node) => addNamed('default', node, getParent(node)),
'ExportSpecifier': (node) => addNamed(
node.exported.name,
node.exported,
getParent(node.parent),
),
'ExportNamedDeclaration': function (node) {
if (node.declaration == null) return;
const parent = getParent(node);
const isTypeVariableDecl = node.declaration.kind === 'type';
if (node.declaration.id != null) {
if (includes([
'TSTypeAliasDeclaration',
'TSInterfaceDeclaration',
], node.declaration.type)) {
addNamed(node.declaration.id.name, node.declaration.id, parent, true);
} else {
addNamed(node.declaration.id.name, node.declaration.id, parent, isTypeVariableDecl);
}
}
if (node.declaration.declarations != null) {
for (const declaration of node.declaration.declarations) {
recursivePatternCapture(declaration.id, v =>
addNamed(v.name, v, parent, isTypeVariableDecl));
}
}
},
'ExportAllDeclaration': function (node) {
if (node.source == null) return;
if (node.exported && node.exported.name) return;
const remoteExports = ExportMap.get(node.source.value, context);
if (remoteExports == null) return;
if (remoteExports.errors.length) {
remoteExports.reportErrors(context, node);
return;
}
const parent = getParent(node);
let any = false;
remoteExports.forEach((v, name) => {
if (name !== 'default') {
any = true;
addNamed(name, node, parent);
}
});
if (!any) {
context.report(
node.source,
`No named exports found in module '${node.source.value}'.`,
);
}
},
'Program:exit': function () {
for (const [, named] of namespace) {
for (const [name, nodes] of named) {
if (nodes.size <= 1) continue;
if (isTypescriptFunctionOverloads(nodes)) continue;
for (const node of nodes) {
if (name === 'default') {
context.report(node, 'Multiple default exports.');
} else {
context.report(
node,
`Multiple exports of name '${name.replace(tsTypePrefix, '')}'.`,
);
}
}
}
}
},
};
},
};