harmonyc
Version:
Harmony Code - model-driven BDD for Vitest
147 lines (146 loc) • 5.25 kB
JavaScript
import * as t from 'ts-morph';
export class PhrasesAssistant {
constructor(content, className) {
var _a;
this.project = new t.Project({
useInMemoryFileSystem: true,
});
this.file = this.project.createSourceFile('filename.ts', content, {
overwrite: true,
});
const clazz = this.file.getClass(className);
if (!clazz) {
const defaultExport = this.file.getDefaultExportSymbol();
if (defaultExport) {
const decl = defaultExport.getDeclarations()[0];
if (t.Node.isClassDeclaration(decl)) {
this.clazz = decl;
this.clazz.rename(className);
}
}
}
else {
this.clazz = clazz;
if (!this.clazz.isDefaultExport()) {
this.clazz.setIsDefaultExport(true);
}
}
(_a = this.clazz) !== null && _a !== void 0 ? _a : (this.clazz = this.file.addClass({
name: className,
isDefaultExport: true,
}));
}
ensureMethods(methods) {
for (const method of methods) {
this.ensureMethod(method);
}
this.clazz
.getMethods()
.filter((m) => !methods.find((md) => md.name === m.getName()))
.forEach((m) => {
if (m.getStatements().length === 1 &&
m
.getStatements()[0]
.getText()
.match(/throw new Error\(["']TODO /)) {
m.remove();
}
});
this.sortMethods();
}
ensureMethod(method) {
let existing = this.clazz.getMethod(method.name);
if (!existing) {
this.addMethod(method);
}
}
addMethod(method) {
const m = this.clazz.addMethod({
isAsync: true,
name: method.name,
parameters: method.parameters,
statements: [`throw new Error("TODO ${method.name}");`],
});
m.formatText({ indentSize: 2 });
}
sortMethods() {
const groups = ['When_', 'Then_'];
const members = this.clazz.getMembersWithComments();
const sorted = members.slice().sort((a, b) => {
const kindA = t.Node.isMethodDeclaration(a)
? groups.findIndex((g) => a.getName().startsWith(g))
: -1;
const kindB = t.Node.isMethodDeclaration(b)
? groups.findIndex((g) => b.getName().startsWith(g))
: -1;
if (kindA !== kindB) {
return kindA - kindB;
}
if (kindA === -1 && kindB === -1) {
return 0;
}
if (!t.Node.isMethodDeclaration(a) || !t.Node.isMethodDeclaration(b)) {
// never happens
return 0;
}
return a.getName() < b.getName() ? -1 : 1;
});
const moves = calculateMoves(members, sorted);
for (const move of moves) {
const method = members[move.fromIndex];
if (t.Node.isCommentClassElement(method)) {
// something went wrong
return;
}
method.setOrder(move.toIndex);
const [moved] = members.splice(move.fromIndex, 1);
members.splice(move.toIndex, 0, moved);
}
}
toCode() {
let s = this.file.getFullText();
// fix extra space
const closing = this.clazz.getEnd() - 1;
if (s.slice(closing - 2, closing + 1) === '\n }') {
s = s.slice(0, closing - 1) + s.slice(closing);
}
return s;
}
}
/**
* Calculates a set of move operations to transform one array into another.
* Both arrays must contain the same elements, just in different order.
*
* @param actual - The current array that needs to be reordered
* @param desired - The target array with the desired order
* @returns Array of move operations {fromIndex, toIndex} that transform actual into desired
*/
function calculateMoves(actual, desired) {
if (actual.length !== desired.length) {
throw new Error('Arrays must have the same length');
}
// Create a working copy to track changes
const working = [...actual];
const moves = [];
// For each position in the desired array
for (let targetIndex = 0; targetIndex < desired.length; targetIndex++) {
const targetElement = desired[targetIndex];
// Find where this element currently is in our working array
const currentIndex = working.indexOf(targetElement);
if (currentIndex === -1) {
throw new Error('Arrays must contain the same elements');
}
// If it's not in the right position, move it
if (currentIndex !== targetIndex) {
// Record the move operation
moves.push({
fromIndex: currentIndex,
toIndex: targetIndex,
});
// Apply the move to our working array
const [movedElement] = working.splice(currentIndex, 1);
working.splice(targetIndex, 0, movedElement);
}
}
return moves;
}