eslint-plugin-import-x
Version:
Import with sanity.
238 lines • 9.32 kB
JavaScript
import { importDeclaration, ExportMap, createRule, declaredScope, getValue, } from '../utils/index.js';
function processBodyStatement(context, namespaces, declaration) {
if (declaration.type !== 'ImportDeclaration') {
return;
}
if (declaration.specifiers.length === 0) {
return;
}
const imports = ExportMap.get(declaration.source.value, context);
if (imports == null) {
return;
}
if (imports.errors.length > 0) {
imports.reportErrors(context, declaration);
return;
}
for (const specifier of declaration.specifiers) {
switch (specifier.type) {
case 'ImportNamespaceSpecifier': {
if (imports.size === 0) {
context.report({
node: specifier,
messageId: 'noNamesFound',
data: {
module: declaration.source.value,
},
});
}
namespaces.set(specifier.local.name, imports);
break;
}
case 'ImportDefaultSpecifier':
case 'ImportSpecifier': {
const meta = imports.get('imported' in specifier
? getValue(specifier.imported)
:
'default');
if (!meta || !meta.namespace) {
break;
}
namespaces.set(specifier.local.name, meta.namespace);
break;
}
default:
}
}
}
function makeMessage(last, namepath, node = last) {
const messageId = namepath.length > 1 ? 'notFoundInNamespaceDeep' : 'notFoundInNamespace';
return {
node,
messageId,
data: {
name: last.name,
namepath: namepath.join('.'),
},
};
}
export default createRule({
name: 'namespace',
meta: {
type: 'problem',
docs: {
category: 'Static analysis',
description: 'Ensure imported namespaces contain dereferenced properties as they are dereferenced.',
},
schema: [
{
type: 'object',
properties: {
allowComputed: {
description: 'If `false`, will report computed (and thus, un-lintable) references to namespace members.',
type: 'boolean',
default: false,
},
},
additionalProperties: false,
},
],
messages: {
noNamesFound: "No exported names found in module '{{module}}'.",
computedReference: "Unable to validate computed reference to imported namespace '{{namespace}}'.",
namespaceMember: "Assignment to member of namespace '{{namespace}}'.",
topLevelNames: 'Only destructure top-level names.',
notFoundInNamespace: "'{{name}}' not found in imported namespace '{{namepath}}'.",
notFoundInNamespaceDeep: "'{{name}}' not found in deeply imported namespace '{{namepath}}'.",
},
},
defaultOptions: [
{
allowComputed: false,
},
],
create(context) {
const { allowComputed } = context.options[0] || {};
const namespaces = new Map();
return {
Program({ body }) {
for (const x of body) {
processBodyStatement(context, namespaces, x);
}
},
ExportNamespaceSpecifier(namespace) {
const declaration = importDeclaration(context, namespace);
const imports = ExportMap.get(declaration.source.value, context);
if (imports == null) {
return null;
}
if (imports.errors.length > 0) {
imports.reportErrors(context, declaration);
return;
}
if (imports.size === 0) {
context.report({
node: namespace,
messageId: 'noNamesFound',
data: {
module: declaration.source.value,
},
});
}
},
MemberExpression(dereference) {
if (dereference.object.type !== 'Identifier') {
return;
}
if (!namespaces.has(dereference.object.name)) {
return;
}
if (declaredScope(context, dereference, dereference.object.name) !==
'module') {
return;
}
const parent = dereference.parent;
if (parent?.type === 'AssignmentExpression' &&
parent.left === dereference) {
context.report({
node: parent,
messageId: 'namespaceMember',
data: {
namespace: dereference.object.name,
},
});
}
let namespace = namespaces.get(dereference.object.name);
const namepath = [dereference.object.name];
let deref = dereference;
while (namespace instanceof ExportMap &&
deref?.type === 'MemberExpression') {
if (deref.computed) {
if (!allowComputed) {
context.report({
node: deref.property,
messageId: 'computedReference',
data: {
namespace: 'name' in deref.object && deref.object.name,
},
});
}
return;
}
if (!namespace.has(deref.property.name)) {
context.report(makeMessage(deref.property, namepath));
break;
}
const exported = namespace.get(deref.property.name);
if (exported == null) {
return;
}
namepath.push(deref.property.name);
namespace = exported.namespace;
deref = deref.parent;
}
},
VariableDeclarator(node) {
const { id, init } = node;
if (init == null) {
return;
}
if (init.type !== 'Identifier') {
return;
}
if (!namespaces.has(init.name)) {
return;
}
if (declaredScope(context, node, init.name) !== 'module') {
return;
}
const initName = init.name;
function testKey(pattern, namespace, path = [initName]) {
if (!(namespace instanceof ExportMap)) {
return;
}
if (pattern.type !== 'ObjectPattern') {
return;
}
for (const property of pattern.properties) {
if (property.type === 'ExperimentalRestProperty' ||
property.type === 'RestElement' ||
!property.key) {
continue;
}
if (property.key.type !== 'Identifier') {
context.report({
node: property,
messageId: 'topLevelNames',
});
continue;
}
if (!namespace.has(property.key.name)) {
context.report(makeMessage(property.key, path, property));
continue;
}
path.push(property.key.name);
const dependencyExportMap = namespace.get(property.key.name);
if (dependencyExportMap != null) {
testKey(property.value, dependencyExportMap.namespace, path);
}
path.pop();
}
}
testKey(id, namespaces.get(init.name));
},
JSXMemberExpression({ object, property }) {
if (!('name' in object) ||
typeof object.name !== 'string' ||
!namespaces.has(object.name)) {
return;
}
const namespace = namespaces.get(object.name);
if (!namespace.has(property.name)) {
context.report(makeMessage(property, [object.name]));
}
},
};
},
});
//# sourceMappingURL=namespace.js.map