eslint-plugin-graphile-export
Version:
ESLint plugin to help ensure your schema is exportable by graphile-export
170 lines • 8.57 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportSubclasses = void 0;
const path_1 = require("path");
const common_js_1 = require("./common.js");
const NoNested_js_1 = require("./NoNested.js");
const KNOWN_IMPORTS = [
["grafast", "Step"],
["grafast", "Modifier"],
];
exports.ExportSubclasses = {
meta: {
type: "suggestion",
docs: {
description: "Looks for classes that extend Step or Modifier.",
recommended: true,
url: "TODO",
},
fixable: "code",
hasSuggestions: true,
schema: [
{
type: "object",
additionalProperties: false,
disableAutofix: false,
properties: {
disableAutofix: {
type: "boolean",
},
},
},
],
},
create(context) {
const disableAutofix = context.options?.[0]?.disableAutofix ?? false;
// const scopeManager = context.sourceCode.scopeManager;
const options = {
disableAutofix,
};
return {
ClassDeclaration(node) {
const superClass = node.superClass;
if (!superClass) {
return;
}
const superClassIdentifier = superClass.type === "Identifier" ? superClass.name : null;
if (!superClassIdentifier) {
return;
}
const possibles = KNOWN_IMPORTS.filter((tuple) => tuple[1] === superClassIdentifier);
if (possibles.length === 0) {
return;
}
const className = node.id?.name;
if (!className) {
return;
}
if ((0, NoNested_js_1.hasExportableParent)(node))
return;
// ENHANCE: if the definition for the `superClass` identifier is not actually an import from any of these `possibles` then we should `return;`. It just happens to share the same name?
//const scope = scopeManager.acquire(node);
const isTypeScript = /\.[mc]?tsx?$/.test(context.getFilename());
const isGeneric = !!node
.typeParameters;
const $$export = node.body.body.find((def) => def.type === "PropertyDefinition" &&
def.key.type === "Identifier" &&
def.key.name === "$$export");
if ($$export) {
if (!$$export.static) {
(0, common_js_1.reportProblem)(context, options, {
node: $$export,
message: `$$export should be a static property; this is probably a mistake?`,
suggest: [
{
desc: "add 'static' keyword",
fix(fixer) {
return [
fixer.replaceTextRange([$$export.range[0], $$export.range[0]], `static `),
];
},
},
],
});
return;
}
if ($$export.value?.type === "ObjectExpression") {
// Validate the object
const moduleName = $$export.value.properties.find((prop) => prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "moduleName");
const exportName = $$export.value.properties.find((prop) => prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "exportName");
const safe = !$$export.value.properties.some((prop) => prop.type === "SpreadElement") &&
!$$export.value.properties.some((prop) => prop.type === "Property" && prop.key.type !== "Identifier");
if (safe && !moduleName) {
(0, common_js_1.reportProblem)(context, options, {
node: $$export,
message: `$$export specifier doesn't explicitly specify 'moduleName'`,
});
}
if (safe && !exportName) {
(0, common_js_1.reportProblem)(context, options, {
node: $$export,
message: `$$export specifier doesn't explicitly specify 'exportName'`,
});
}
if (moduleName && exportName) {
if (!moduleName.value ||
!["StringLiteral", "Literal"].includes(moduleName.value.type)) {
console.dir(moduleName.value);
(0, common_js_1.reportProblem)(context, options, {
node: $$export,
message: `$$export has invalid value for 'moduleName' - expected a string literal.`,
});
}
if (!exportName.value ||
!["StringLiteral", "Literal"].includes(exportName.value.type)) {
(0, common_js_1.reportProblem)(context, options, {
node: $$export,
message: `$$export has invalid value for 'exportName' - expected a string literal.`,
});
}
}
}
else {
// Assume it's all good.
}
return;
}
const convertToImportable = {
desc: "convert to importable",
fix(fixer) {
return [
fixer.replaceTextRange([node.body.range[0] + 1, node.body.range[0] + 1], `\n static $$export = { moduleName: /* TODO! */ ${JSON.stringify(`./${(0, path_1.basename)(context.getFilename())}`)}, exportName: "${className}" };\n`),
];
},
};
const convertToExportable = {
desc: "convert to exportable",
fix(fixer) {
return [
fixer.replaceTextRange([node.range[0], node.range[0]], `const ${className} = EXPORTABLE(() => `),
fixer.replaceTextRange([node.range[1], node.range[1]], ", []);" +
(isTypeScript
? `\ntype ${className} = InstanceType<typeof ${className}>;`
: ``)),
];
},
};
const exportExample = `static $$export = { moduleName: /* TODO */ ".../path/to/module.js", exportName: "${className}" }`;
if (isGeneric) {
(0, common_js_1.reportProblem)(context, options, {
node: node,
message: `Generic class '${className}' extending '${superClassIdentifier}' cannot be safely exported. Because it's generic we cannot make it EXPORTABLE, instead make it importable by putting it in its own module and declaring a '${exportExample}' property.`,
suggest: [convertToImportable],
});
}
else {
(0, common_js_1.reportProblem)(context, options, {
node: node,
message: `Class '${className}' extends '${superClassIdentifier}' but cannot be safely exported. Either make it EXPORTABLE or make it importable and add a '${exportExample}' property to indicate this.`,
suggest: [convertToExportable, convertToImportable],
});
}
},
};
},
};
//# sourceMappingURL=ExportSubclasses.js.map
;