@itrocks/uses
Version:
Apply reusable mixins to your classes effortlessly with the @Uses decorator
163 lines • 8.06 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const typescript_1 = __importDefault(require("typescript"));
class UpdateOptions {
createImports = new Map;
updateClasses = new Set;
createExport = '';
createInterfaces = new Map;
}
let updateDeclarations = new Map;
function usesDecoratorValues(node) {
const mixins = [];
for (const decorator of typescript_1.default.getDecorators(node) ?? []) {
if (!typescript_1.default.isCallExpression(decorator.expression))
continue;
if (decorator.expression.expression.getText() !== 'Uses')
continue;
for (const argument of decorator.expression.arguments) {
if (!typescript_1.default.isIdentifier(argument))
continue;
mixins.push(argument.text);
}
}
return mixins;
}
exports.default = () => function transformer(context) {
const { factory } = context;
function createExport(className) {
return factory.createExportAssignment(undefined, undefined, factory.createIdentifier(className));
}
function createInterface(node, className, mixins) {
const modifiers = [];
if (node.modifiers?.some(modifier => (modifier.kind === typescript_1.default.SyntaxKind.ExportKeyword))) {
modifiers.push(factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword));
}
const interfaceName = factory.createIdentifier(className);
const heritageClause = factory.createHeritageClause(typescript_1.default.SyntaxKind.ExtendsKeyword, mixins.map(mixin => factory.createExpressionWithTypeArguments(factory.createIdentifier(mixin), undefined)));
return factory.createInterfaceDeclaration(modifiers, interfaceName, undefined, [heritageClause], []);
}
function updateClass(node) {
const modifiers = node.modifiers?.filter(modifier => (modifier.kind !== typescript_1.default.SyntaxKind.DefaultKeyword)) ?? [];
modifiers.push(factory.createModifier(typescript_1.default.SyntaxKind.DeclareKeyword));
return factory.updateClassDeclaration(node, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members);
}
function visitSourceFile(sourceFile) {
const imports = new Map;
const updateOptions = new UpdateOptions;
const alreadyInterfaces = new Set;
function visit(node) {
if (typescript_1.default.isImportDeclaration(node) && node.importClause && node.moduleSpecifier) {
const importPath = node.moduleSpecifier.text;
const namedBindings = node.importClause.namedBindings;
const name = node.importClause.name;
if (name) {
imports.set(name.text, { default: true, path: importPath });
}
if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
namedBindings.elements.forEach(element => {
imports.set(element.name.text, { default: false, path: importPath });
});
}
return node;
}
if (typescript_1.default.isClassDeclaration(node)) {
let className;
let mixins;
if (!(className = node.name?.text)
|| !typescript_1.default.canHaveDecorators(node)
|| !(mixins = usesDecoratorValues(node)).length) {
return node;
}
const isDefault = node.modifiers?.some(modifier => (modifier.kind === typescript_1.default.SyntaxKind.DefaultKeyword));
if (isDefault) {
updateOptions.updateClasses.add(className);
}
if (!alreadyInterfaces.has(className)) {
updateOptions.createInterfaces.set(className, mixins);
for (const mixin of mixins) {
const importOptions = imports.get(mixin);
if (importOptions) {
updateOptions.createImports.set(mixin, importOptions);
}
}
}
if (isDefault) {
updateOptions.createExport = className;
}
updateDeclarations.set(sourceFile.fileName, updateOptions);
return node;
}
if (typescript_1.default.isInterfaceDeclaration(node)) {
const className = node.name?.text;
if (className && node.modifiers?.some(modifier => (modifier.kind === typescript_1.default.SyntaxKind.ExportKeyword))) {
alreadyInterfaces.add(className);
updateOptions.createInterfaces.delete(className);
}
return node;
}
return typescript_1.default.visitEachChild(node, visit, context);
}
return typescript_1.default.visitNode(sourceFile, visit);
}
function visitDeclarationFile(sourceFile) {
const updateOptions = updateDeclarations.get(sourceFile.fileName);
if (!updateOptions)
return sourceFile;
let doneImports = false;
const imports = new Set;
function visit(node) {
if (typescript_1.default.isSourceFile(node))
return typescript_1.default.visitEachChild(node, visit, context);
if (typescript_1.default.isImportDeclaration(node)) {
if (node.importClause && node.moduleSpecifier) {
const namedBindings = node.importClause.namedBindings;
const name = node.importClause.name;
if (name)
imports.add(name.text);
if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
namedBindings.elements.forEach(element => imports.add(element.name.text));
}
return node;
}
return node;
}
const nodes = [];
const options = updateOptions;
if (!doneImports) {
doneImports = true;
options.createImports.forEach((importOptions, mixin) => {
if (imports.has(mixin))
return;
nodes.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, importOptions.default ? factory.createIdentifier(mixin) : undefined, importOptions.default ? undefined : factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier(mixin))
])), factory.createStringLiteral(importOptions.path)));
});
}
if (typescript_1.default.isClassDeclaration(node)) {
const className = node.name?.text;
if (className) {
nodes.push(options.updateClasses.has(className) ? updateClass(node) : node);
const mixins = options.createInterfaces.get(className);
if (mixins?.length) {
nodes.push(createInterface(node, className, mixins));
}
if (options.createExport) {
nodes.push(createExport(className));
}
}
}
return (nodes.length) ? [...nodes] : node;
}
const result = typescript_1.default.visitNode(sourceFile, visit);
updateDeclarations.delete(sourceFile.fileName);
return result;
}
return (sourceFile) => sourceFile.isDeclarationFile
? visitDeclarationFile(sourceFile)
: visitSourceFile(sourceFile);
};
//# sourceMappingURL=uses-interface-plugin.js.map