Socket
Socket
Sign inDemoInstall

automutate

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

automutate - npm Package Compare versions

Comparing version 0.7.3 to 0.8.0

.babelrc.json

10

.vscode/settings.json
{
"editor.tabSize": 4,
"editor.trimAutoWhitespace": true,
"tslint.alwaysShowRuleFailuresAsWarnings": true,
"tslint.autoFixOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
"editor.tabSize": 4,
"editor.trimAutoWhitespace": true,
"tslint.alwaysShowRuleFailuresAsWarnings": true,
"tslint.autoFixOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

@@ -13,9 +13,9 @@ # Mutators

```typescript
import { IMutation } from "automutate";
import { Mutation } from "automutate";
export interface ITextInsertSmileyMutation extends IMutation {
/**
* Unique type name identifying smiley insertion mutations.
*/
type: "text-insert-smiley";
export interface TextInsertSmileyMutation extends Mutation {
/**
* Unique type name identifying smiley insertion mutations.
*/
type: "text-insert-smiley";
}

@@ -30,9 +30,9 @@ ```

export class InsertSmileyMutator {
mutate(fileContents, mutation) {
return [
fileContents.substring(0, mutation.range.begin),
smiley,
fileContents.substring(mutation.range.begin),
].join("");
}
mutate(fileContents, mutation) {
return [
fileContents.substring(0, mutation.range.begin),
smiley,
fileContents.substring(mutation.range.begin),
].join("");
}
}

@@ -54,3 +54,3 @@ ```

Mutators are also given the *original* file contents at construction time, which allows for custom mutators to perform setup logic.
Mutators are also given the _original_ file contents at construction time, which allows for custom mutators to perform setup logic.
For example, a language's linter might create an abstract syntax tree for the file.

@@ -61,19 +61,19 @@

```typescript
import { IMutation, IMutationRange } from "automutate";
import { Mutation, MutationRange } from "automutate";
export interface INodeRenameMutation extends IMutation {
/**
* New name for the node.
*/
newName: string;
export interface NodeRenameMutation extends Mutation {
/**
* New name for the node.
*/
newName: string;
/**
* AST node being renamed.
*/
node: IMutationRange;
/**
* AST node being renamed.
*/
node: MutationRange;
/**
* Unique type name identifying node rename mutations.
*/
type: "node-name";
/**
* Unique type name identifying node rename mutations.
*/
type: "node-name";
}

@@ -89,22 +89,22 @@ ```

export class NodeRenameMutator {
constructor(originalFileContents) {
super(originalFileContents);
constructor(originalFileContents) {
super(originalFileContents);
this.ast = new AbstractSyntaxTree(originalFileContents);
}
this.ast = new AbstractSyntaxTree(originalFileContents);
}
mutate(fileContents, mutation) {
const node = this.ast.getNodeAt(mutation.node.begin, mutation.node.end);
node.rename(mutation.newName);
mutate(fileContents, mutation) {
const node = this.ast.getNodeAt(mutation.node.begin, mutation.node.end);
node.rename(mutation.newName);
for (const nodeReference of this.ast.getNodeReferences(node)) {
nodeReference.rename(mutation.newName);
}
for (const nodeReference of this.ast.getNodeReferences(node)) {
nodeReference.rename(mutation.newName);
}
return [
fileContents.substring(0, mutation.range.begin),
this.ast.stringifyBetween(mutation.range.begin, mutation.range.end),
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
return [
fileContents.substring(0, mutation.range.begin),
this.ast.stringifyBetween(mutation.range.begin, mutation.range.end),
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
}

@@ -111,0 +111,0 @@ ```

@@ -10,3 +10,2 @@ # Onboarding

## Optional Preqrequisites

@@ -28,11 +27,11 @@

Automutate works best when it does the least.
Have your linter provide fix suggestions in some format so that all your autolinter needs to do is convert them to waves of `IMutation`s.
Have your linter provide fix suggestions in some format so that all your autolinter needs to do is convert them to waves of `Mutation`s.
If your linter does not yet provide fix suggestions, you'll need to write converter logic to generate `IMutation`s from the lint output.
If your linter does not yet provide fix suggestions, you'll need to write converter logic to generate `Mutation`s from the lint output.
That's doable but carries two major downsides:
* Your logic will be at risk getting out of sync.
* If your linter is updated to produce different output, you'll need to separately update the converter logic.
* Users may not know they need to update their autolinter along with your linter, and end up with bad autolinting.
* Fix suggestions won't have the full information used to generate complaints, such as abstract syntax trees or source files.
- Your logic will be at risk getting out of sync.
- If your linter is updated to produce different output, you'll need to separately update the converter logic.
- Users may not know they need to update their autolinter along with your linter, and end up with bad autolinting.
- Fix suggestions won't have the full information used to generate complaints, such as abstract syntax trees or source files.

@@ -42,3 +41,3 @@ ## Technical Implementation

Automutation is driven by an instance of the [`AutoMutator` class](../src/autoMutator.ts).
It requires an [`IMutationsProvider`](../src/mutationsProvider.ts) to generate suggestions that will be applied to files.
It requires an [`MutationsProvider`](../src/mutationsProvider.ts) to generate suggestions that will be applied to files.

@@ -53,11 +52,11 @@ A base setup would look something like:

export function createMyAutomutator() {
return new AutoMutator({
mutationsProvider: new SmileyMutationsProvider(),
});
return new AutoMutator({
mutationsProvider: new SmileyMutationsProvider(),
});
}
```
### `IMutationsProvider`
### `MutationsProvider`
An `IMutationsProvider` must implement a `provide()` method that returns a `Promise` for an `IMutationsWave`.
An `MutationsProvider` must implement a `provide()` method that returns a `Promise` for an `MutationsWave`.
See [`mutationsProvider.ts`](../src/mutationsProvider.ts) for the interface definitions.

@@ -76,25 +75,25 @@

export class SmileyMutationsProvider {
async provide() {
const data = await fs.readFile("my-file.txt");
async provide() {
const data = await fs.readFile("my-file.txt");
return this.generateMutations(data.toString());
return this.generateMutations(data.toString());
}
generateMutations(text) {
if (text.substring(smiley.length) === smiley) {
return {};
}
generateMutations(text) {
if (text.substring(smiley.length) === smiley) {
return {};
}
return {
fileMutations: [
{
insertion: smiley,
range: {
begin: 0
},
type: "text-insert",
},
],
};
}
return {
fileMutations: [
{
insertion: smiley,
range: {
begin: 0,
},
type: "text-insert",
},
],
};
}
}

@@ -101,0 +100,0 @@ ```

@@ -1,23 +0,12 @@

import { IMutationRunSettings } from "./runMutations";
import { MutationRunSettings } from "./runMutations";
/**
* Settings to initialize a new IAutoMutator.
* Settings to initialize a new AutoMutator.
*/
export declare type IAutoMutatorSettings = IMutationRunSettings;
export declare type AutoMutatorSettings = MutationRunSettings;
/**
* Runs waves of file mutations.
*/
export interface IAutoMutator {
/**
* Runs waves of file mutations.
*
* @returns A Promise for the waves completing.
*/
run(): Promise<void>;
}
/**
* Runs waves of file mutations.
*
* @deprecated Use `runMutations` from ./runMutations instead.
*/
export declare class AutoMutator implements IAutoMutator {
export declare class AutoMutator {
/**

@@ -40,3 +29,3 @@ * Generates output messages for significant operations.

*/
constructor(settings: IAutoMutatorSettings);
constructor(settings: AutoMutatorSettings);
/**

@@ -43,0 +32,0 @@ * Runs waves of file mutations.

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var consoleLogger_1 = require("./loggers/consoleLogger");
var fileMutationsApplier_1 = require("./mutationsAppliers/fileMutationsApplier");
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoMutator = void 0;
const consoleLogger_1 = require("./loggers/consoleLogger");
const fileMutationsApplier_1 = require("./mutationsAppliers/fileMutationsApplier");
/**

@@ -45,3 +11,3 @@ * Runs waves of file mutations.

*/
var AutoMutator = /** @class */ (function () {
class AutoMutator {
/**

@@ -52,7 +18,9 @@ * Initializes a new instance of the AutoMutator class.

*/
function AutoMutator(settings) {
constructor(settings) {
this.logger = settings.logger || new consoleLogger_1.ConsoleLogger();
this.mutationsApplier = settings.mutationsApplier || new fileMutationsApplier_1.FileMutationsApplier({
logger: this.logger
});
this.mutationsApplier =
settings.mutationsApplier ||
new fileMutationsApplier_1.FileMutationsApplier({
logger: this.logger,
});
this.mutationsProvider = settings.mutationsProvider;

@@ -65,29 +33,15 @@ }

*/
AutoMutator.prototype.run = function () {
return __awaiter(this, void 0, void 0, function () {
var mutationsWave;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!true) return [3 /*break*/, 3];
return [4 /*yield*/, this.mutationsProvider.provide()];
case 1:
mutationsWave = _a.sent();
if (!mutationsWave.fileMutations) {
return [3 /*break*/, 3];
}
this.logger.onWaveBegin(mutationsWave);
return [4 /*yield*/, this.mutationsApplier.apply(mutationsWave.fileMutations)];
case 2:
_a.sent();
this.logger.onWaveEnd(mutationsWave);
return [3 /*break*/, 0];
case 3: return [2 /*return*/];
}
});
});
};
return AutoMutator;
}());
async run() {
while (true) {
const mutationsWave = await this.mutationsProvider.provide();
if (!mutationsWave.fileMutations) {
break;
}
this.logger.onWaveBegin(mutationsWave);
await this.mutationsApplier.apply(mutationsWave.fileMutations);
this.logger.onWaveEnd(mutationsWave);
}
}
}
exports.AutoMutator = AutoMutator;
//# sourceMappingURL=autoMutator.js.map

@@ -0,0 +0,0 @@ /**

"use strict";
exports.__esModule = true;
//# sourceMappingURL=fileProvider.js.map

@@ -0,0 +0,0 @@ import { IFileProvider } from "./fileProvider";

"use strict";
exports.__esModule = true;
exports.FileProviderFactory = void 0;
/**

@@ -4,0 +5,0 @@ * Generates file providers for files.

@@ -1,6 +0,6 @@

import { IFileProvider } from "../fileProvider";
import { FileProvider } from "../types/fileProvider";
/**
* Provides read-write operations on a local file.
*/
export declare class LocalFileProvider implements IFileProvider {
export declare class LocalFileProvider implements FileProvider {
/**

@@ -7,0 +7,0 @@ * Name of the file.

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var fs = require("fs");
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalFileProvider = void 0;
const fs = __importStar(require("fs"));
/**
* Provides read-write operations on a local file.
*/
var LocalFileProvider = /** @class */ (function () {
class LocalFileProvider {
/**

@@ -49,3 +34,3 @@ * Initializes a new instance of the LocalFileProvider class.

*/
function LocalFileProvider(fileName) {
constructor(fileName) {
this.fileName = fileName;

@@ -58,16 +43,9 @@ }

*/
LocalFileProvider.prototype.read = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve, reject) {
fs.readFile(_this.fileName, function (error, data) {
error
? reject(error)
: resolve(data.toString());
});
})];
async read() {
return new Promise((resolve, reject) => {
fs.readFile(this.fileName, (error, data) => {
error ? reject(error) : resolve(data.toString());
});
});
};
}
/**

@@ -79,24 +57,11 @@ * Writes to the file.

*/
LocalFileProvider.prototype.write = function (contents) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new Promise(function (resolve, reject) {
fs.writeFile(_this.fileName, contents, function (error) {
error
? reject(error)
: resolve();
});
})];
case 1:
_a.sent();
return [2 /*return*/];
}
async write(contents) {
await new Promise((resolve, reject) => {
fs.writeFile(this.fileName, contents, (error) => {
error ? reject(error) : resolve();
});
});
};
return LocalFileProvider;
}());
}
}
exports.LocalFileProvider = LocalFileProvider;
//# sourceMappingURL=localFileProvider.js.map

@@ -1,6 +0,6 @@

import { IFileProvider } from "../fileProvider";
import { FileProvider } from "../types/fileProvider";
/**
* Pretends to be a file.
*/
export declare class StubFileProvider implements IFileProvider {
export declare class StubFileProvider implements FileProvider {
/**

@@ -7,0 +7,0 @@ * Contents of the file.

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.StubFileProvider = void 0;
/**
* Pretends to be a file.
*/
var StubFileProvider = /** @class */ (function () {
class StubFileProvider {
/**

@@ -47,3 +13,3 @@ * Initializes a new instance of the StubFileProvider class.

*/
function StubFileProvider(contents) {
constructor(contents) {
this.contents = contents;

@@ -56,9 +22,5 @@ }

*/
StubFileProvider.prototype.read = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.contents];
});
});
};
async read() {
return this.contents;
}
/**

@@ -70,13 +32,7 @@ * Writes to the file.

*/
StubFileProvider.prototype.write = function (contents) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
this.contents = contents;
return [2 /*return*/];
});
});
};
return StubFileProvider;
}());
async write(contents) {
this.contents = contents;
}
}
exports.StubFileProvider = StubFileProvider;
//# sourceMappingURL=stubFileProvider.js.map
export * from "./autoMutator";
export * from "./logger";
export * from "./fileProviderFactories/cachingFileProviderFactory";
export * from "./fileProviders/localFileProvider";
export * from "./fileProviders/stubFileProvider";
export * from "./loggers/consoleLogger";
export * from "./mutation";
export * from "./loggers/noopLogger";
export * from "./mutationsAppliers/fileMutationsApplier";

@@ -9,6 +11,13 @@ export * from "./mutationsProvider";

export * from "./mutators/multipleMutator";
export * from "./mutators/textSwapMutator";
export * from "./mutators/textDeleteMutator";
export * from "./mutators/textInsertMutator";
export * from "./mutators/textReplaceMutator";
export * from "./mutators/textSwapMutator";
export * from "./runMutations";
export * from "./types/fileProvider";
export * from "./types/fileProviderFactory";
export * from "./types/logger";
export * from "./types/logger";
export * from "./types/mutation";
export * from "./types/mutationsApplier";
export * from "./types/mutatorSearcher";
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
__export(require("./autoMutator"));
__export(require("./logger"));
__export(require("./loggers/consoleLogger"));
__export(require("./mutationsAppliers/fileMutationsApplier"));
__export(require("./mutator"));
__export(require("./mutators/multipleMutator"));
__export(require("./mutators/textSwapMutator"));
__export(require("./mutators/textDeleteMutator"));
__export(require("./mutators/textInsertMutator"));
__export(require("./mutators/textReplaceMutator"));
__export(require("./runMutations"));
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./autoMutator"), exports);
__exportStar(require("./fileProviderFactories/cachingFileProviderFactory"), exports);
__exportStar(require("./fileProviders/localFileProvider"), exports);
__exportStar(require("./fileProviders/stubFileProvider"), exports);
__exportStar(require("./loggers/consoleLogger"), exports);
__exportStar(require("./loggers/noopLogger"), exports);
__exportStar(require("./mutationsAppliers/fileMutationsApplier"), exports);
__exportStar(require("./mutationsProvider"), exports);
__exportStar(require("./mutator"), exports);
__exportStar(require("./mutators/multipleMutator"), exports);
__exportStar(require("./mutators/textDeleteMutator"), exports);
__exportStar(require("./mutators/textInsertMutator"), exports);
__exportStar(require("./mutators/textReplaceMutator"), exports);
__exportStar(require("./mutators/textSwapMutator"), exports);
__exportStar(require("./runMutations"), exports);
__exportStar(require("./types/fileProvider"), exports);
__exportStar(require("./types/fileProviderFactory"), exports);
__exportStar(require("./types/logger"), exports);
__exportStar(require("./types/logger"), exports);
__exportStar(require("./types/mutation"), exports);
__exportStar(require("./types/mutationsApplier"), exports);
__exportStar(require("./types/mutatorSearcher"), exports);
//# sourceMappingURL=index.js.map

@@ -0,0 +0,0 @@ import { IMutation } from "./mutation";

"use strict";
exports.__esModule = true;
exports.Logger = void 0;
/**

@@ -4,0 +5,0 @@ * Default no-op class to generate output messages for significant operations.

@@ -1,8 +0,23 @@

import { Logger } from "../logger";
import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Logger } from "../types/logger";
/**
* Generates console logs for significant operations.
*/
export declare class ConsoleLogger extends Logger {
export declare class ConsoleLogger implements Logger {
/**
* Mutations applied to each file, keyed by file name.
*/
private readonly fileMutations;
/**
* Waves of file mutations.
*/
private readonly mutationsWaves;
/**
* Logs that a mutation was applied.
*
* @param fileName Name of the file to be mutated.
* @param mutation The requesting mutation.
*/
onMutation(fileName: string, mutation: Mutation): void;
/**
* Logs that mutations have completed.

@@ -16,4 +31,12 @@ */

*/
onUnknownMutationType(mutation: IMutation): void;
onUnknownMutationType(mutation: Mutation): void;
/**
* Logs that a mutations wave is about to start.
*/
onWaveBegin(): void;
/**
* Logs that a mutations wave finished.
*/
onWaveEnd(): void;
/**
* Displays a word and number, accounting for pluralization.

@@ -20,0 +43,0 @@ *

"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var logger_1 = require("../logger");
// tslint:disable:no-console
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleLogger = void 0;
/**
* Generates console logs for significant operations.
*/
var ConsoleLogger = /** @class */ (function (_super) {
__extends(ConsoleLogger, _super);
function ConsoleLogger() {
return _super !== null && _super.apply(this, arguments) || this;
class ConsoleLogger {
constructor() {
/**
* Mutations applied to each file, keyed by file name.
*/
this.fileMutations = {};
/**
* Waves of file mutations.
*/
this.mutationsWaves = [];
}
/**
* Logs that a mutation was applied.
*
* @param fileName Name of the file to be mutated.
* @param mutation The requesting mutation.
*/
onMutation(fileName, mutation) {
if (this.fileMutations[fileName]) {
this.fileMutations[fileName].push(mutation);
}
else {
this.fileMutations[fileName] = [mutation];
}
}
/**
* Logs that mutations have completed.
*/
ConsoleLogger.prototype.onComplete = function () {
_super.prototype.onComplete.call(this);
var fileMutations = this.getFileMutations();
var filesCount = Object.keys(fileMutations).length;
var mutationsCount = Object.keys(fileMutations)
.map(function (fileName) { return fileMutations[fileName].length; })
.reduce(function (a, b) { return a + b; }, 0);
var wavesCount = this.getMutationsWaves().length;
onComplete() {
const fileMutations = this.fileMutations;
const filesCount = Object.keys(fileMutations).length;
const mutationsCount = Object.keys(fileMutations)
.map((fileName) => fileMutations[fileName].length)
.reduce((a, b) => a + b, 0);
const wavesCount = this.mutationsWaves.length;
console.log([

@@ -46,3 +51,3 @@ "Completed ",

].join(""));
};
}
/**

@@ -53,7 +58,18 @@ * Logs that an unknown mutator was requested.

*/
ConsoleLogger.prototype.onUnknownMutationType = function (mutation) {
_super.prototype.onUnknownMutationType.call(this, mutation);
console.error("Unknown mutator type: '" + mutation.type + "'");
};
onUnknownMutationType(mutation) {
console.error(`Unknown mutator type: '${mutation.type}'`);
}
/**
* Logs that a mutations wave is about to start.
*/
onWaveBegin() {
console.log("Starting new wave of mutations...");
}
/**
* Logs that a mutations wave finished.
*/
onWaveEnd() {
console.log("Ending wave.");
}
/**
* Displays a word and number, accounting for pluralization.

@@ -64,11 +80,7 @@ *

*/
ConsoleLogger.prototype.pluralize = function (count, word) {
return count === 1
? count + " " + word
: count + " " + word + "s";
};
return ConsoleLogger;
}(logger_1.Logger));
pluralize(count, word) {
return count === 1 ? `${count} ${word}` : `${count} ${word}s`;
}
}
exports.ConsoleLogger = ConsoleLogger;
// tslint:enable:no-console
//# sourceMappingURL=consoleLogger.js.map

@@ -0,0 +0,0 @@ /**

"use strict";
exports.__esModule = true;
//# sourceMappingURL=mutation.js.map

@@ -0,0 +0,0 @@ import { IFileProviderFactory } from "./fileProviderFactory";

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());

@@ -38,2 +39,3 @@ });

exports.__esModule = true;
exports.MutationsApplier = void 0;
var ordering_1 = require("./ordering");

@@ -40,0 +42,0 @@ /**

@@ -1,15 +0,15 @@

import { ILogger } from "../logger";
import { MutationsApplier } from "../mutationsApplier";
import { MutationsApplier, MutationsApplierSettings } from "../types/mutationsApplier";
import { Logger } from "../types/logger";
/**
* Settings to apply individual waves of file mutations to local files.
*/
export interface IFileMutationsApplierSettings {
export interface FileMutationsApplierSettings extends Partial<MutationsApplierSettings> {
/**
* Generates output messages for significant operations.
* Additional directories to search for mutators within.
*/
logger: ILogger;
mutatorDirectories?: string[];
/**
* Additional directories to search for mutators within.
* Generates output messages for significant operations.
*/
mutatorDirectories?: string[];
logger: Logger;
}

@@ -25,3 +25,3 @@ /**

*/
constructor(settings: IFileMutationsApplierSettings);
constructor(settings: FileMutationsApplierSettings);
}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var path = require("path");
var fileProviderFactory_1 = require("../fileProviderFactory");
var localFileProvider_1 = require("../fileProviders/localFileProvider");
var mutationsApplier_1 = require("../mutationsApplier");
var mutatorFactory_1 = require("../mutatorFactory");
var mutatorSearcher_1 = require("../mutatorSearcher");
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileMutationsApplier = void 0;
const path = __importStar(require("path"));
const localFileProvider_1 = require("../fileProviders/localFileProvider");
const mutationsApplier_1 = require("../types/mutationsApplier");
const mutatorFactory_1 = require("../mutatorFactory");
const cachingFileProviderFactory_1 = require("../fileProviderFactories/cachingFileProviderFactory");
const commonJSMutatorSearcher_1 = require("../mutatorSearchers/commonJSMutatorSearcher");
/**
* Applies individual waves of file mutations to local files.
*/
var FileMutationsApplier = /** @class */ (function (_super) {
__extends(FileMutationsApplier, _super);
class FileMutationsApplier extends mutationsApplier_1.MutationsApplier {
/**

@@ -32,14 +38,16 @@ * Initializes a new instance of the FileMutationsApplier class.

*/
function FileMutationsApplier(settings) {
return _super.call(this, {
fileProviderFactory: new fileProviderFactory_1.FileProviderFactory(function (fileName) { return new localFileProvider_1.LocalFileProvider(fileName); }),
constructor(settings) {
super({
fileProviderFactory: settings.fileProviderFactory ??
new cachingFileProviderFactory_1.CachingFileProviderFactory((fileName) => new localFileProvider_1.LocalFileProvider(fileName)),
logger: settings.logger,
mutatorFactory: new mutatorFactory_1.MutatorFactory(new mutatorSearcher_1.MutatorSearcher([
path.join(__dirname, "../../lib/mutators")
].concat((settings.mutatorDirectories || []))), settings.logger)
}) || this;
mutatorFactory: settings.mutatorFactory ??
new mutatorFactory_1.MutatorFactory(new commonJSMutatorSearcher_1.CommonJSMutatorSearcher([
path.join(__dirname, "../../lib/mutators"),
...(settings.mutatorDirectories || []),
]), settings.logger),
});
}
return FileMutationsApplier;
}(mutationsApplier_1.MutationsApplier));
}
exports.FileMutationsApplier = FileMutationsApplier;
//# sourceMappingURL=fileMutationsApplier.js.map

@@ -1,7 +0,7 @@

import { IMutation } from "./mutation";
import { Mutation } from "./types/mutation";
/**
* Mutations to be applied to files, keyed by file name.
*/
export interface IFileMutations {
[i: string]: ReadonlyArray<IMutation>;
export interface FileMutations {
[i: string]: ReadonlyArray<Mutation>;
}

@@ -11,7 +11,7 @@ /**

*/
export interface IMutationsWave {
export interface MutationsWave {
/**
* Mutations to be applied to files, if any.
*/
readonly fileMutations?: IFileMutations;
readonly fileMutations?: FileMutations;
}

@@ -21,7 +21,7 @@ /**

*/
export interface IMutationsProvider {
export interface MutationsProvider {
/**
* @returns A Promise for a wave of file mutations.
*/
readonly provide: () => Promise<IMutationsWave>;
readonly provide: () => Promise<MutationsWave>;
}
"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=mutationsProvider.js.map

@@ -1,3 +0,3 @@

import { IMutation } from "./mutation";
import { IMutatorFactory } from "./mutatorFactory";
import { Mutation } from "./types/mutation";
import { MutatorFactory } from "./mutatorFactory";
/**

@@ -25,3 +25,3 @@ * Applies a type of mutation to a file.

*/
abstract mutate(fileContents: string, mutation: IMutation, mutatorFactory: IMutatorFactory): string;
abstract mutate(fileContents: string, mutation: Mutation, mutatorFactory: MutatorFactory): string;
/**

@@ -28,0 +28,0 @@ * Gets the original contents of the file.

"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mutator = void 0;
/**
* Applies a type of mutation to a file.
*/
var Mutator = /** @class */ (function () {
class Mutator {
/**

@@ -12,3 +13,3 @@ * Initializes a new instance of the Mutator class.

*/
function Mutator(originalFileContents) {
constructor(originalFileContents) {
this.originalFileContents = originalFileContents;

@@ -21,8 +22,7 @@ }

*/
Mutator.prototype.getOriginalFileContents = function () {
getOriginalFileContents() {
return this.originalFileContents;
};
return Mutator;
}());
}
}
exports.Mutator = Mutator;
//# sourceMappingURL=mutator.js.map

@@ -1,31 +0,10 @@

import { ILogger } from "./logger";
import { IMutation } from "./mutation";
import { Logger } from "./types/logger";
import { Mutation } from "./types/mutation";
import { Mutator } from "./mutator";
import { IMutatorSearcher } from "./mutatorSearcher";
import { MutatorSearcher } from "./types/mutatorSearcher";
/**
* Creates mutators for mutations.
*/
export interface IMutatorFactory {
export declare class MutatorFactory {
/**
* Attempts to find and instantiate a mutator sub-class for a file.
*
* @param name Dashed-case name of the mutator sub-class.
* @param fileContents Contents of the file.
* @returns An instance of the mutator sub-class, if the sub-class can be found.
*/
generate<TMutator extends Mutator>(name: string, fileContents: string): TMutator | undefined;
/**
* Generates and applies a mutator, if possible.
*
* @param fileContents Contents of the file.
* @param mutation Mutation to be applied to the file.
* @returns The mutated file contents.
*/
generateAndApply(fileContents: string, mutation: IMutation): string;
}
/**
* Creates mutators for mutations.
*/
export declare class MutatorFactory implements IMutatorFactory {
/**
* Mutator sub-classes, keyed by dashed-case name.

@@ -47,3 +26,3 @@ */

*/
constructor(mutatorSearcher: IMutatorSearcher, logger: ILogger);
constructor(mutatorSearcher: MutatorSearcher, logger: Logger);
/**

@@ -64,3 +43,3 @@ * Attempts to find and instantiate a mutator sub-class for a file.

*/
generateAndApply(fileContents: string, mutation: IMutation): string;
generateAndApply(fileContents: string, mutation: Mutation): string;
}
"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MutatorFactory = void 0;
/**
* Creates mutators for mutations.
*/
var MutatorFactory = /** @class */ (function () {
class MutatorFactory {
/**

@@ -12,3 +13,3 @@ * Initializes a new instance of the MutatorFactory class.

*/
function MutatorFactory(mutatorSearcher, logger) {
constructor(mutatorSearcher, logger) {
/**

@@ -28,5 +29,5 @@ * Mutator sub-classes, keyed by dashed-case name.

*/
MutatorFactory.prototype.generate = function (name, fileContents) {
generate(name, fileContents) {
if (!this.classes[name]) {
var mutatorClass = this.searcher.search(name);
const mutatorClass = this.searcher.search(name);
if (!mutatorClass) {

@@ -38,4 +39,4 @@ return undefined;

// @todo Use some form of "implements" keyword when TypeScript supports it
return new (this.classes[name])(fileContents);
};
return new this.classes[name](fileContents);
}
/**

@@ -48,4 +49,4 @@ * Generates and applied a mutator, if possible.

*/
MutatorFactory.prototype.generateAndApply = function (fileContents, mutation) {
var mutator = this.generate(mutation.type, fileContents);
generateAndApply(fileContents, mutation) {
const mutator = this.generate(mutation.type, fileContents);
if (!mutator) {

@@ -56,6 +57,5 @@ this.logger.onUnknownMutationType(mutation);

return mutator.mutate(fileContents, mutation, this);
};
return MutatorFactory;
}());
}
}
exports.MutatorFactory = MutatorFactory;
//# sourceMappingURL=mutatorFactory.js.map

@@ -1,12 +0,12 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";
import { IMutatorFactory } from "../mutatorFactory";
import { MutatorFactory } from "../mutatorFactory";
/**
* Multiple mutations to be applied together.
*/
export interface IMultipleMutations extends IMutation {
export interface MultipleMutations extends Mutation {
/**
* Mutations to be applied together.
*/
mutations: IMutation[];
mutations: Mutation[];
/**

@@ -28,3 +28,3 @@ * Unique type name identifying multiple mutations.

*/
mutate(fileContents: string, mutation: IMultipleMutations, mutatorFactory: IMutatorFactory): string;
mutate(fileContents: string, mutation: MultipleMutations, mutatorFactory: MutatorFactory): string;
}

@@ -38,2 +38,2 @@ /**

*/
export declare const combineMutations: (...mutations: IMutation[]) => IMultipleMutations;
export declare const combineMutations: (...mutations: Mutation[]) => MultipleMutations;
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var mutator_1 = require("../mutator");
var ordering_1 = require("../ordering");
Object.defineProperty(exports, "__esModule", { value: true });
exports.combineMutations = exports.MultipleMutator = void 0;
const mutator_1 = require("../mutator");
const ordering_1 = require("../ordering");
/**
* Applies multiple mutations to a file.
*/
var MultipleMutator = /** @class */ (function (_super) {
__extends(MultipleMutator, _super);
function MultipleMutator() {
return _super !== null && _super.apply(this, arguments) || this;
}
class MultipleMutator extends mutator_1.Mutator {
/**

@@ -33,11 +17,9 @@ * Applies a mutation.

*/
MultipleMutator.prototype.mutate = function (fileContents, mutation, mutatorFactory) {
for (var _i = 0, _a = ordering_1.orderMutationsLastToFirst(mutation.mutations); _i < _a.length; _i++) {
var childMutation = _a[_i];
mutate(fileContents, mutation, mutatorFactory) {
for (const childMutation of ordering_1.orderMutationsLastToFirst(mutation.mutations)) {
fileContents = mutatorFactory.generateAndApply(fileContents, childMutation);
}
return fileContents;
};
return MultipleMutator;
}(mutator_1.Mutator));
}
}
exports.MultipleMutator = MultipleMutator;

@@ -51,11 +33,6 @@ /**

*/
exports.combineMutations = function () {
var mutations = [];
for (var _i = 0; _i < arguments.length; _i++) {
mutations[_i] = arguments[_i];
}
var begin = mutations[0].range.begin;
var end = mutations[0].range.end;
for (var _a = 0, mutations_1 = mutations; _a < mutations_1.length; _a++) {
var range = mutations_1[_a].range;
exports.combineMutations = (...mutations) => {
let begin = mutations[0].range.begin;
let end = mutations[0].range.end;
for (const { range } of mutations) {
begin = Math.min(begin, range.begin);

@@ -65,12 +42,13 @@ if (range.end !== undefined && (end === undefined || range.end > end)) {

}
end = end === undefined
? Math.max(begin, range.begin)
: Math.max(end, begin, range.begin);
end =
end === undefined
? Math.max(begin, range.begin)
: Math.max(end, begin, range.begin);
}
return {
mutations: ordering_1.orderMutationsFirstToLast(mutations),
range: { begin: begin, end: end },
type: "multiple"
range: { begin, end },
type: "multiple",
};
};
//# sourceMappingURL=multipleMutator.js.map

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -6,3 +6,3 @@ /**

*/
export interface ITextDeleteMutation extends IMutation {
export interface TextDeleteMutation extends Mutation {
/**

@@ -24,3 +24,3 @@ * Unique type name identifying text delete mutations.

*/
mutate(fileContents: string, mutation: ITextDeleteMutation): string;
mutate(fileContents: string, mutation: TextDeleteMutation): string;
}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var mutator_1 = require("../mutator");
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextDeleteMutator = void 0;
const mutator_1 = require("../mutator");
/**
* Applies text deletion mutations to a file.
*/
var TextDeleteMutator = /** @class */ (function (_super) {
__extends(TextDeleteMutator, _super);
function TextDeleteMutator() {
return _super !== null && _super.apply(this, arguments) || this;
}
class TextDeleteMutator extends mutator_1.Mutator {
/**

@@ -32,3 +16,3 @@ * Applies a mutation.

*/
TextDeleteMutator.prototype.mutate = function (fileContents, mutation) {
mutate(fileContents, mutation) {
return [

@@ -38,6 +22,5 @@ fileContents.substring(0, mutation.range.begin),

].join("");
};
return TextDeleteMutator;
}(mutator_1.Mutator));
}
}
exports.TextDeleteMutator = TextDeleteMutator;
//# sourceMappingURL=textDeleteMutator.js.map

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -6,3 +6,3 @@ /**

*/
export interface ITextInsertMutation extends IMutation {
export interface TextInsertMutation extends Mutation {
/**

@@ -28,3 +28,3 @@ * Text to be inserted.

*/
mutate(fileContents: string, mutation: ITextInsertMutation): string;
mutate(fileContents: string, mutation: TextInsertMutation): string;
}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var mutator_1 = require("../mutator");
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextInsertMutator = void 0;
const mutator_1 = require("../mutator");
/**
* Applies text insertion mutations to a file.
*/
var TextInsertMutator = /** @class */ (function (_super) {
__extends(TextInsertMutator, _super);
function TextInsertMutator() {
return _super !== null && _super.apply(this, arguments) || this;
}
class TextInsertMutator extends mutator_1.Mutator {
/**

@@ -32,3 +16,3 @@ * Applies a mutation.

*/
TextInsertMutator.prototype.mutate = function (fileContents, mutation) {
mutate(fileContents, mutation) {
return [

@@ -39,6 +23,5 @@ fileContents.substring(0, mutation.range.begin),

].join("");
};
return TextInsertMutator;
}(mutator_1.Mutator));
}
}
exports.TextInsertMutator = TextInsertMutator;
//# sourceMappingURL=textInsertMutator.js.map

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -6,3 +6,3 @@ /**

*/
export interface ITextReplaceMutation extends IMutation {
export interface TextReplaceMutation extends Mutation {
/**

@@ -32,3 +32,3 @@ * String to be inserted.

*/
mutate(fileContents: string, mutation: ITextReplaceMutation): string;
mutate(fileContents: string, mutation: TextReplaceMutation): string;
}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var mutator_1 = require("../mutator");
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextReplaceMutator = void 0;
const mutator_1 = require("../mutator");
/**
* Applies text replace mutations to a file.
*/
var TextReplaceMutator = /** @class */ (function (_super) {
__extends(TextReplaceMutator, _super);
function TextReplaceMutator() {
return _super !== null && _super.apply(this, arguments) || this;
}
class TextReplaceMutator extends mutator_1.Mutator {
/**

@@ -32,8 +16,7 @@ * Applies a mutation.

*/
TextReplaceMutator.prototype.mutate = function (fileContents, mutation) {
mutate(fileContents, mutation) {
return fileContents.replace(new RegExp(mutation.search, "g"), mutation.replace);
};
return TextReplaceMutator;
}(mutator_1.Mutator));
}
}
exports.TextReplaceMutator = TextReplaceMutator;
//# sourceMappingURL=textReplaceMutator.js.map

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -6,3 +6,3 @@ /**

*/
export interface ITextSwapMutation extends IMutation {
export interface TextSwapMutation extends Mutation {
/**

@@ -28,3 +28,3 @@ * Text to be inserted.

*/
mutate(fileContents: string, mutation: ITextSwapMutation): string;
mutate(fileContents: string, mutation: TextSwapMutation): string;
}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var mutator_1 = require("../mutator");
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextSwapMutator = void 0;
const mutator_1 = require("../mutator");
/**
* Applies text swap mutations to a file.
*/
var TextSwapMutator = /** @class */ (function (_super) {
__extends(TextSwapMutator, _super);
function TextSwapMutator() {
return _super !== null && _super.apply(this, arguments) || this;
}
class TextSwapMutator extends mutator_1.Mutator {
/**

@@ -32,3 +16,3 @@ * Applies a mutation.

*/
TextSwapMutator.prototype.mutate = function (fileContents, mutation) {
mutate(fileContents, mutation) {
return [

@@ -39,6 +23,5 @@ fileContents.substring(0, mutation.range.begin),

].join("");
};
return TextSwapMutator;
}(mutator_1.Mutator));
}
}
exports.TextSwapMutator = TextSwapMutator;
//# sourceMappingURL=textSwapMutator.js.map

@@ -0,0 +0,0 @@ import { Mutator } from "./mutator";

"use strict";
exports.__esModule = true;
exports.MutatorSearcher = void 0;
var fs = require("fs");

@@ -4,0 +5,0 @@ var path = require("path");

/**
* Transforms dashed-case names to cases.
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
export interface INameTransformer {
/**
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
toCamelCase(name: string): string;
/**
* Transforms a dashed-case name to PamelCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
toPascalCase(name: string): string;
}
export declare const toCamelCase: (name: string) => string;
/**
* Transforms dashed-case names to cases.
* Transforms a dashed-case name to PascalCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
export declare class NameTransformer implements INameTransformer {
/**
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
toCamelCase(name: string): string;
/**
* Transforms a dashed-case name to PamelCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
toPascalCase(name: string): string;
}
export declare const toPascalCase: (name: string) => string;
"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.toPascalCase = exports.toCamelCase = void 0;
/**
* Transforms dashed-case names to cases.
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
var NameTransformer = /** @class */ (function () {
function NameTransformer() {
}
/**
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
NameTransformer.prototype.toCamelCase = function (name) {
var split = name.split("-");
return split[0].toLowerCase() + split
exports.toCamelCase = (name) => {
const split = name.split("-");
return (split[0].toLowerCase() +
split
.slice(1)
.map(function (part) {
return part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase();
})
.join("");
};
/**
* Transforms a dashed-case name to PamelCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
NameTransformer.prototype.toPascalCase = function (name) {
return name.split("-")
.map(function (part) {
return part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase();
})
.join("");
};
return NameTransformer;
}());
exports.NameTransformer = NameTransformer;
.map((part) => part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join(""));
};
/**
* Transforms a dashed-case name to PascalCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
exports.toPascalCase = (name) => {
return name
.split("-")
.map((part) => part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join("");
};
//# sourceMappingURL=nameTransformer.js.map

@@ -1,2 +0,2 @@

import { IMutation } from "./mutation";
import { Mutation } from "./types/mutation";
/**

@@ -8,3 +8,3 @@ * Orders a set of mutations first-to-last.

*/
export declare const orderMutationsFirstToLast: (mutations: ReadonlyArray<IMutation>) => IMutation[];
export declare const orderMutationsFirstToLast: (mutations: ReadonlyArray<Mutation>) => Mutation[];
/**

@@ -16,3 +16,3 @@ * Orders a set of mutations last-to-first.

*/
export declare const orderMutationsLastToFirst: (mutations: ReadonlyArray<IMutation>) => IMutation[];
export declare const orderMutationsLastToFirst: (mutations: ReadonlyArray<Mutation>) => Mutation[];
/**

@@ -24,2 +24,2 @@ * Orders a set of mutations last-to-first, without overlaps.

*/
export declare const orderMutationsLastToFirstWithoutOverlaps: (mutations: ReadonlyArray<IMutation>) => IMutation[];
export declare const orderMutationsLastToFirstWithoutOverlaps: (mutations: ReadonlyArray<Mutation>) => Mutation[];
"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.orderMutationsLastToFirstWithoutOverlaps = exports.orderMutationsLastToFirst = exports.orderMutationsFirstToLast = void 0;
/**

@@ -9,7 +10,5 @@ * Orders a set of mutations first-to-last.

*/
exports.orderMutationsFirstToLast = function (mutations) {
return mutations.slice().sort(function (a, b) {
return (a.range.end || a.range.begin) - (b.range.end || b.range.begin);
});
};
exports.orderMutationsFirstToLast = (mutations) => mutations
.slice()
.sort((a, b) => (a.range.end || a.range.begin) - (b.range.end || b.range.begin));
/**

@@ -21,7 +20,5 @@ * Orders a set of mutations last-to-first.

*/
exports.orderMutationsLastToFirst = function (mutations) {
return mutations.slice().sort(function (a, b) {
return (b.range.end || b.range.begin) - (a.range.end || a.range.begin);
});
};
exports.orderMutationsLastToFirst = (mutations) => mutations
.slice()
.sort((a, b) => (b.range.end || b.range.begin) - (a.range.end || a.range.begin));
/**

@@ -33,8 +30,8 @@ * Orders a set of mutations last-to-first, without overlaps.

*/
exports.orderMutationsLastToFirstWithoutOverlaps = function (mutations) {
var ordered = exports.orderMutationsFirstToLast(mutations);
var orderedWithoutOverlaps = [];
var lastStart = Infinity;
for (var i = ordered.length - 1; i >= 0; i -= 1) {
var mutation = ordered[i];
exports.orderMutationsLastToFirstWithoutOverlaps = (mutations) => {
const ordered = exports.orderMutationsFirstToLast(mutations);
const orderedWithoutOverlaps = [];
let lastStart = Infinity;
for (let i = ordered.length - 1; i >= 0; i -= 1) {
const mutation = ordered[i];
if ((mutation.range.end || mutation.range.begin) > lastStart) {

@@ -41,0 +38,0 @@ continue;

@@ -1,20 +0,21 @@

import { ILogger } from "./logger";
import { IMutationsApplier } from "./mutationsApplier";
import { IMutationsProvider } from "./mutationsProvider";
import { AutoMutatorSettings } from "./autoMutator";
import { Logger } from "./types/logger";
import { MutationsApplier } from "./types/mutationsApplier";
import { MutationsProvider } from "./mutationsProvider";
/**
* Settings to run waves of mutations.
*/
export interface IMutationRunSettings {
export interface MutationRunSettings {
/**
* Generates output messages for significant operations.
*/
logger?: ILogger;
logger?: Logger;
/**
* Applies individual waves of file mutations.
*/
mutationsApplier?: IMutationsApplier;
mutationsApplier?: MutationsApplier;
/**
* Provides waves of file mutations.
*/
mutationsProvider: IMutationsProvider;
mutationsProvider: MutationsProvider;
}

@@ -24,3 +25,3 @@ /**

*/
export interface IMutationRunResults {
export interface MutationRunResults {
/**

@@ -36,2 +37,2 @@ * Names of all files that were mutated at least once.

*/
export declare const runMutations: (settings: IMutationRunSettings) => Promise<IMutationRunResults>;
export declare const runMutations: (settings: AutoMutatorSettings) => Promise<MutationRunResults>;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var _this = this;
exports.__esModule = true;
var consoleLogger_1 = require("./loggers/consoleLogger");
var fileMutationsApplier_1 = require("./mutationsAppliers/fileMutationsApplier");
Object.defineProperty(exports, "__esModule", { value: true });
exports.runMutations = void 0;
const consoleLogger_1 = require("./loggers/consoleLogger");
const fileMutationsApplier_1 = require("./mutationsAppliers/fileMutationsApplier");
/**

@@ -46,35 +11,22 @@ * Runs waves of mutations.

*/
exports.runMutations = function (settings) { return __awaiter(_this, void 0, void 0, function () {
var logger, mutatedFileNames, mutationsApplier, mutationsWave, _i, _a, fileName;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
logger = settings.logger || new consoleLogger_1.ConsoleLogger();
mutatedFileNames = new Set();
mutationsApplier = settings.mutationsApplier || new fileMutationsApplier_1.FileMutationsApplier({ logger: logger });
_b.label = 1;
case 1:
if (!true) return [3 /*break*/, 4];
return [4 /*yield*/, settings.mutationsProvider.provide()];
case 2:
mutationsWave = _b.sent();
if (mutationsWave.fileMutations === undefined) {
return [3 /*break*/, 4];
}
logger.onWaveBegin(mutationsWave);
return [4 /*yield*/, mutationsApplier.apply(mutationsWave.fileMutations)];
case 3:
_b.sent();
logger.onWaveEnd(mutationsWave);
for (_i = 0, _a = Object.keys(mutationsWave.fileMutations); _i < _a.length; _i++) {
fileName = _a[_i];
mutatedFileNames.add(fileName);
}
return [3 /*break*/, 1];
case 4: return [2 /*return*/, {
mutatedFileNames: Array.from(mutatedFileNames)
}];
exports.runMutations = async (settings) => {
const logger = settings.logger || new consoleLogger_1.ConsoleLogger();
const mutatedFileNames = new Set();
const mutationsApplier = settings.mutationsApplier || new fileMutationsApplier_1.FileMutationsApplier({ logger });
while (true) {
const mutationsWave = await settings.mutationsProvider.provide();
if (mutationsWave.fileMutations === undefined) {
break;
}
});
}); };
logger.onWaveBegin(mutationsWave);
await mutationsApplier.apply(mutationsWave.fileMutations);
logger.onWaveEnd(mutationsWave);
for (const fileName of Object.keys(mutationsWave.fileMutations)) {
mutatedFileNames.add(fileName);
}
}
return {
mutatedFileNames: Array.from(mutatedFileNames),
};
};
//# sourceMappingURL=runMutations.js.map

@@ -0,0 +0,0 @@ Permission is hereby granted, free of charge, to any person obtaining

{
"name": "automutate",
"version": "0.7.3",
"version": "0.8.0",
"description": "Applies waves of mutations provided by other tools, such as linters.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"test": "test"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/es6-promise": "3.3.0",
"@types/glob": "^7.1.1",
"@types/glob-stream": "^6.1.0",
"@types/minimatch": "^3.0.3",
"@types/mocha": "^5.2.5",
"@types/sinon": "^7.0.0",
"chai": "^4.2.0",
"del": "^3.0.0",
"glob": "^7.1.3",
"mocha": "^5.2.0",
"sinon": "^7.2.2",
"tslint": "^5.11.0",
"tsutils": "^3.5.2",
"typescript": "^3.2.2"
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@types/jest": "^25.2.3",
"@typescript-eslint/eslint-plugin": "3.0.0",
"@typescript-eslint/parser": "^3.0.0",
"babel-jest": "^27.3.1",
"eslint": "^7.1.0",
"eslint-config-prettier": "^6.11.0",
"husky": "^4.2.5",
"jest": "^27.3.1",
"lint-staged": "^10.2.6",
"prettier": "^2.0.5",
"typescript": "^3.9.3"
},
"scripts": {
"src": "npm run src:tsc && npm run src:tslint",
"src:tsc": "tsc -p .",
"src:tslint": "tslint -c tslint.json -p tsconfig.json -t stylish",
"test": "npm run test:tsc && npm run test:run",
"test:tsc": "tsc -p test",
"test:run": "mocha test/**/*.js",
"verify": "npm run src && npm run test"
"compile": "tsc --module commonjs",
"format": "yarn prettier --write",
"format:verify": "yarn prettier --list-different \"**/*.{js,json,md,ts,yml}\"",
"format:write-all": "yarn format:verify --write",
"lint": "eslint \"./src/*.ts\" \"./src/**/*.ts\" --max-warnings 0 --report-unused-disable-directives",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
},

@@ -36,0 +31,0 @@ "repository": {

# automutate
[![Greenkeeper badge](https://badges.greenkeeper.io/automutate/automutate.svg)](https://greenkeeper.io/)
[![Build Status](https://travis-ci.org/automutate/automutate.svg?branch=master)](https://travis-ci.org/automutate/automutate)

@@ -11,15 +10,16 @@ [![npm](https://img.shields.io/npm/v/automutate.svg)](https://www.npmjs.com/package/automutate)

This is great but hard to do for a couple of reasons:
* **Overlapping mutations** - The possibility of mutations appling to overlapping sets of characters requires logic to handle applying one, then re-running linting, and so on.
* **Code bloat verses duplication** - Most linters either provide hooks to apply fixes themselves (which can result in code bloat) or have an external project (which duplicates logic for finding rules).
- **Overlapping mutations** - The possibility of mutations appling to overlapping sets of characters requires logic to handle applying one, then re-running linting, and so on.
- **Code bloat verses duplication** - Most linters either provide hooks to apply fixes themselves (which can result in code bloat) or have an external project (which duplicates logic for finding rules).
`automutate` proposes that linters only propose **how** to fix rules, via a standardized JSON format.
Having a standardized source-agnostic project to apply mutations brings a couple of benefits:
* **Reduced overhead** - Projects no longer need to do this work themselves.
* **Standardized base** - Ramp-up time to switch between projects using `automutate` is reduced with common code.
In general, *detecting* rule failures is a separate concern from *fixing* them.
- **Reduced overhead** - Projects no longer need to do this work themselves.
- **Standardized base** - Ramp-up time to switch between projects using `automutate` is reduced with common code.
In general, _detecting_ rule failures is a separate concern from _fixing_ them.
Linters need to run quickly over a read-only set of files, often during built processes, while fixers typically run slowly and modify files on user request.
## How it works

@@ -39,5 +39,4 @@

3. `getNonOverlappingMutationsInReverse` removes overlapping mutations that would conflict with each other, and sorts the remainder in reverse order so that later mutations don't interfere with character positions of earlier mutations.
4. `applyMutation` modifies files on disk using the remaining mutations.
4. `applyMutation` modifies files on disk using the remaining mutations.
## Mutations

@@ -48,8 +47,9 @@

The following basic text manipulations are provided out of the box:
* **`multiple`** - Container for multiple mutations. This indicates to `automutate` that these must be applied all at once or not at all, which guarantees consistency with the built-in mutation overlap detection.
* **`text-delete`** - Deletes a range of characters.
* **`text-insert`** - Inserts a string at a point.
* **`text-replace`** - Replaces characters matching a string or regular expression within a range.
* **`text-swap`** - Swaps a range of characters with a new string.
- **`multiple`** - Container for multiple mutations. This indicates to `automutate` that these must be applied all at once or not at all, which guarantees consistency with the built-in mutation overlap detection.
- **`text-delete`** - Deletes a range of characters.
- **`text-insert`** - Inserts a string at a point.
- **`text-replace`** - Replaces characters matching a string or regular expression within a range.
- **`text-swap`** - Swaps a range of characters with a new string.
For example:

@@ -59,18 +59,18 @@

{
"ugly-file.txt": [
{
"range": {
"begin": 7,
"end": 14
},
"type": "text-delete"
},
{
"insertion": "inconceivable!",
"range": {
"begin": 21
},
"type": "text-insert"
}
]
"ugly-file.txt": [
{
"range": {
"begin": 7,
"end": 14
},
"type": "text-delete"
},
{
"insertion": "inconceivable!",
"range": {
"begin": 21
},
"type": "text-insert"
}
]
}

@@ -84,3 +84,2 @@ ```

# Project Onboarding

@@ -87,0 +86,0 @@

@@ -1,76 +0,67 @@

import { ILogger } from "./logger";
import { Logger } from "./types/logger";
import { ConsoleLogger } from "./loggers/consoleLogger";
import { IMutationsApplier } from "./mutationsApplier";
import { MutationsApplier } from "./types/mutationsApplier";
import { FileMutationsApplier } from "./mutationsAppliers/fileMutationsApplier";
import { IMutationsProvider, IMutationsWave } from "./mutationsProvider";
import { IMutationRunSettings } from "./runMutations";
import { MutationsProvider, MutationsWave } from "./mutationsProvider";
import { MutationRunSettings } from "./runMutations";
/**
* Settings to initialize a new IAutoMutator.
* Settings to initialize a new AutoMutator.
*/
export type IAutoMutatorSettings = IMutationRunSettings;
export type AutoMutatorSettings = MutationRunSettings;
/**
* Runs waves of file mutations.
*/
export interface IAutoMutator {
/**
* Runs waves of file mutations.
*
* @returns A Promise for the waves completing.
*/
run(): Promise<void>;
}
/**
* Runs waves of file mutations.
*
* @deprecated Use `runMutations` from ./runMutations instead.
*/
export class AutoMutator implements IAutoMutator {
/**
* Generates output messages for significant operations.
*/
private readonly logger: ILogger;
export class AutoMutator {
/**
* Generates output messages for significant operations.
*/
private readonly logger: Logger;
/**
* Applies individual waves of file mutations.
*/
private readonly mutationsApplier: IMutationsApplier;
/**
* Applies individual waves of file mutations.
*/
private readonly mutationsApplier: MutationsApplier;
/**
* Provides waves of file mutations.
*/
private readonly mutationsProvider: IMutationsProvider;
/**
* Provides waves of file mutations.
*/
private readonly mutationsProvider: MutationsProvider;
/**
* Initializes a new instance of the AutoMutator class.
*
* @param settings Settings to be used for initialization.
*/
public constructor(settings: IAutoMutatorSettings) {
this.logger = settings.logger || new ConsoleLogger();
this.mutationsApplier = settings.mutationsApplier || new FileMutationsApplier({
logger: this.logger,
});
this.mutationsProvider = settings.mutationsProvider;
}
/**
* Initializes a new instance of the AutoMutator class.
*
* @param settings Settings to be used for initialization.
*/
public constructor(settings: AutoMutatorSettings) {
this.logger = settings.logger || new ConsoleLogger();
this.mutationsApplier =
settings.mutationsApplier ||
new FileMutationsApplier({
logger: this.logger,
});
this.mutationsProvider = settings.mutationsProvider;
}
/**
* Runs waves of file mutations.
*
* @returns A Promise for the waves completing.
*/
public async run(): Promise<void> {
while (true) {
const mutationsWave: IMutationsWave = await this.mutationsProvider.provide();
if (!mutationsWave.fileMutations) {
break;
}
/**
* Runs waves of file mutations.
*
* @returns A Promise for the waves completing.
*/
public async run(): Promise<void> {
while (true) {
const mutationsWave: MutationsWave =
await this.mutationsProvider.provide();
if (!mutationsWave.fileMutations) {
break;
}
this.logger.onWaveBegin(mutationsWave);
await this.mutationsApplier.apply(mutationsWave.fileMutations);
this.logger.onWaveEnd(mutationsWave);
}
this.logger.onWaveBegin(mutationsWave);
await this.mutationsApplier.apply(mutationsWave.fileMutations);
this.logger.onWaveEnd(mutationsWave);
}
}
}
import * as fs from "fs";
import { IFileProvider } from "../fileProvider";
import { FileProvider } from "../types/fileProvider";

@@ -8,48 +8,44 @@ /**

*/
export class LocalFileProvider implements IFileProvider {
/**
* Name of the file.
*/
private readonly fileName: string;
export class LocalFileProvider implements FileProvider {
/**
* Name of the file.
*/
private readonly fileName: string;
/**
* Initializes a new instance of the LocalFileProvider class.
*
* @param fileName Name of the file.
* @param fileSettings Settings for manipulating local files.
*/
public constructor(fileName: string) {
this.fileName = fileName;
}
/**
* Initializes a new instance of the LocalFileProvider class.
*
* @param fileName Name of the file.
* @param fileSettings Settings for manipulating local files.
*/
public constructor(fileName: string) {
this.fileName = fileName;
}
/**
* Reads from the file.
*
* @returns A Promise for the contents of the file.
*/
public async read(): Promise<string> {
return new Promise<string>((resolve, reject): void => {
fs.readFile(this.fileName, (error: Error, data: Buffer): void => {
error
? reject(error)
: resolve(data.toString());
});
});
}
/**
* Reads from the file.
*
* @returns A Promise for the contents of the file.
*/
public async read(): Promise<string> {
return new Promise<string>((resolve, reject) => {
fs.readFile(this.fileName, (error, data) => {
error ? reject(error) : resolve(data.toString());
});
});
}
/**
* Writes to the file.
*
* @param contents New contents of the file.
* @returns A Promise for writing to the file.
*/
public async write(contents: string): Promise<void> {
await new Promise<void>((resolve, reject): void => {
fs.writeFile(this.fileName, contents, (error: Error): void => {
error
? reject(error)
: resolve();
});
});
}
/**
* Writes to the file.
*
* @param contents New contents of the file.
* @returns A Promise for writing to the file.
*/
public async write(contents: string): Promise<void> {
await new Promise<void>((resolve, reject) => {
fs.writeFile(this.fileName, contents, (error) => {
error ? reject(error) : resolve();
});
});
}
}

@@ -1,2 +0,2 @@

import { IFileProvider } from "../fileProvider";
import { FileProvider } from "../types/fileProvider";

@@ -6,35 +6,35 @@ /**

*/
export class StubFileProvider implements IFileProvider {
/**
* Contents of the file.
*/
private contents: string;
export class StubFileProvider implements FileProvider {
/**
* Contents of the file.
*/
private contents: string;
/**
* Initializes a new instance of the StubFileProvider class.
*
* @param contents Initial contents of the file.
*/
public constructor(contents: string) {
this.contents = contents;
}
/**
* Initializes a new instance of the StubFileProvider class.
*
* @param contents Initial contents of the file.
*/
public constructor(contents: string) {
this.contents = contents;
}
/**
* Reads from the file.
*
* @returns A Promise for the contents of the file.
*/
public async read(): Promise<string> {
return this.contents;
}
/**
* Reads from the file.
*
* @returns A Promise for the contents of the file.
*/
public async read(): Promise<string> {
return this.contents;
}
/**
* Writes to the file.
*
* @param contents New contents of the file.
* @returns A Promise for writing to the file.
*/
public async write(contents: string): Promise<void> {
this.contents = contents;
}
/**
* Writes to the file.
*
* @param contents New contents of the file.
* @returns A Promise for writing to the file.
*/
public async write(contents: string): Promise<void> {
this.contents = contents;
}
}
export * from "./autoMutator";
export * from "./logger";
export * from "./fileProviderFactories/cachingFileProviderFactory";
export * from "./fileProviders/localFileProvider";
export * from "./fileProviders/stubFileProvider";
export * from "./loggers/consoleLogger";
export * from "./mutation";
export * from "./loggers/noopLogger";
export * from "./mutationsAppliers/fileMutationsApplier";

@@ -9,6 +11,13 @@ export * from "./mutationsProvider";

export * from "./mutators/multipleMutator";
export * from "./mutators/textSwapMutator";
export * from "./mutators/textDeleteMutator";
export * from "./mutators/textInsertMutator";
export * from "./mutators/textReplaceMutator";
export * from "./mutators/textSwapMutator";
export * from "./runMutations";
export * from "./types/fileProvider";
export * from "./types/fileProviderFactory";
export * from "./types/logger";
export * from "./types/logger";
export * from "./types/mutation";
export * from "./types/mutationsApplier";
export * from "./types/mutatorSearcher";

@@ -1,59 +0,89 @@

import { Logger } from "../logger";
import { IMutation } from "../mutation";
import { IFileMutations } from "../mutationsProvider";
import { Mutation } from "../types/mutation";
import { FileMutations, MutationsWave } from "../mutationsProvider";
import { Logger } from "../types/logger";
// tslint:disable:no-console
/**
* Generates console logs for significant operations.
*/
export class ConsoleLogger extends Logger {
/**
* Logs that mutations have completed.
*/
public onComplete(): void {
super.onComplete();
export class ConsoleLogger implements Logger {
/**
* Mutations applied to each file, keyed by file name.
*/
private readonly fileMutations: { [i: string]: Mutation[] } = {};
const fileMutations: IFileMutations = this.getFileMutations();
const filesCount: number = Object.keys(fileMutations).length;
const mutationsCount: number = Object.keys(fileMutations)
.map((fileName: string): number => fileMutations[fileName].length)
.reduce((a: number, b: number): number => a + b, 0);
const wavesCount: number = this.getMutationsWaves().length;
/**
* Waves of file mutations.
*/
private readonly mutationsWaves: MutationsWave[] = [];
console.log([
"Completed ",
this.pluralize(mutationsCount, "mutation"),
" across ",
this.pluralize(filesCount, "file"),
" in ",
this.pluralize(wavesCount, "wave"),
".",
].join(""));
/**
* Logs that a mutation was applied.
*
* @param fileName Name of the file to be mutated.
* @param mutation The requesting mutation.
*/
public onMutation(fileName: string, mutation: Mutation): void {
if (this.fileMutations[fileName]) {
this.fileMutations[fileName].push(mutation);
} else {
this.fileMutations[fileName] = [mutation];
}
}
/**
* Logs that an unknown mutator was requested.
*
* @param mutation The requesting mutation of unknown type.
*/
public onUnknownMutationType(mutation: IMutation): void {
super.onUnknownMutationType(mutation);
/**
* Logs that mutations have completed.
*/
public onComplete(): void {
const fileMutations: FileMutations = this.fileMutations;
const filesCount: number = Object.keys(fileMutations).length;
const mutationsCount: number = Object.keys(fileMutations)
.map((fileName: string): number => fileMutations[fileName].length)
.reduce((a: number, b: number): number => a + b, 0);
const wavesCount: number = this.mutationsWaves.length;
console.error(`Unknown mutator type: '${mutation.type}'`);
}
console.log(
[
"Completed ",
this.pluralize(mutationsCount, "mutation"),
" across ",
this.pluralize(filesCount, "file"),
" in ",
this.pluralize(wavesCount, "wave"),
".",
].join("")
);
}
/**
* Displays a word and number, accounting for pluralization.
*
* @param count How many of the word there are.
* @param word A word to display.
*/
private pluralize(count: number, word: string) {
return count === 1
? `${count} ${word}`
: `${count} ${word}s`;
}
/**
* Logs that an unknown mutator was requested.
*
* @param mutation The requesting mutation of unknown type.
*/
public onUnknownMutationType(mutation: Mutation): void {
console.error(`Unknown mutator type: '${mutation.type}'`);
}
/**
* Logs that a mutations wave is about to start.
*/
public onWaveBegin(): void {
console.log("Starting new wave of mutations...");
}
/**
* Logs that a mutations wave finished.
*/
public onWaveEnd(): void {
console.log("Ending wave.");
}
/**
* Displays a word and number, accounting for pluralization.
*
* @param count How many of the word there are.
* @param word A word to display.
*/
private pluralize(count: number, word: string) {
return count === 1 ? `${count} ${word}` : `${count} ${word}s`;
}
}
// tslint:enable:no-console
import * as path from "path";
import { IFileProvider } from "../fileProvider";
import { FileProviderFactory } from "../fileProviderFactory";
import { FileProvider } from "../types/fileProvider";
import { LocalFileProvider } from "../fileProviders/localFileProvider";
import { ILogger } from "../logger";
import { MutationsApplier } from "../mutationsApplier";
import {
MutationsApplier,
MutationsApplierSettings,
} from "../types/mutationsApplier";
import { MutatorFactory } from "../mutatorFactory";
import { MutatorSearcher } from "../mutatorSearcher";
import { CachingFileProviderFactory } from "../fileProviderFactories/cachingFileProviderFactory";
import { CommonJSMutatorSearcher } from "../mutatorSearchers/commonJSMutatorSearcher";
import { Logger } from "../types/logger";

@@ -14,12 +17,13 @@ /**

*/
export interface IFileMutationsApplierSettings {
/**
* Generates output messages for significant operations.
*/
logger: ILogger;
export interface FileMutationsApplierSettings
extends Partial<MutationsApplierSettings> {
/**
* Additional directories to search for mutators within.
*/
mutatorDirectories?: string[];
/**
* Additional directories to search for mutators within.
*/
mutatorDirectories?: string[];
/**
* Generates output messages for significant operations.
*/
logger: Logger;
}

@@ -31,20 +35,26 @@

export class FileMutationsApplier extends MutationsApplier {
/**
* Initializes a new instance of the FileMutationsApplier class.
*
* @param settings Settings to be used for initialization.
*/
public constructor(settings: IFileMutationsApplierSettings) {
super({
fileProviderFactory: new FileProviderFactory(
(fileName: string): IFileProvider => new LocalFileProvider(fileName)),
logger: settings.logger,
mutatorFactory: new MutatorFactory(
new MutatorSearcher([
path.join(__dirname, "../../lib/mutators"),
...(settings.mutatorDirectories || []),
]),
settings.logger),
});
}
/**
* Initializes a new instance of the FileMutationsApplier class.
*
* @param settings Settings to be used for initialization.
*/
public constructor(settings: FileMutationsApplierSettings) {
super({
fileProviderFactory:
settings.fileProviderFactory ??
new CachingFileProviderFactory(
(fileName: string): FileProvider => new LocalFileProvider(fileName)
),
logger: settings.logger,
mutatorFactory:
settings.mutatorFactory ??
new MutatorFactory(
new CommonJSMutatorSearcher([
path.join(__dirname, "../../lib/mutators"),
...(settings.mutatorDirectories || []),
]),
settings.logger
),
});
}
}

@@ -1,2 +0,2 @@

import { IMutation } from "./mutation";
import { Mutation } from "./types/mutation";

@@ -6,4 +6,4 @@ /**

*/
export interface IFileMutations {
[i: string]: ReadonlyArray<IMutation>;
export interface FileMutations {
[i: string]: ReadonlyArray<Mutation>;
}

@@ -14,7 +14,7 @@

*/
export interface IMutationsWave {
/**
* Mutations to be applied to files, if any.
*/
readonly fileMutations?: IFileMutations;
export interface MutationsWave {
/**
* Mutations to be applied to files, if any.
*/
readonly fileMutations?: FileMutations;
}

@@ -25,7 +25,7 @@

*/
export interface IMutationsProvider {
/**
* @returns A Promise for a wave of file mutations.
*/
readonly provide: () => Promise<IMutationsWave>;
export interface MutationsProvider {
/**
* @returns A Promise for a wave of file mutations.
*/
readonly provide: () => Promise<MutationsWave>;
}

@@ -1,3 +0,3 @@

import { IMutation } from "./mutation";
import { IMutatorFactory } from "./mutatorFactory";
import { Mutation } from "./types/mutation";
import { MutatorFactory } from "./mutatorFactory";

@@ -8,34 +8,38 @@ /**

export abstract class Mutator {
/**
* Original contents of the file.
*/
private readonly originalFileContents: string;
/**
* Original contents of the file.
*/
private readonly originalFileContents: string;
/**
* Initializes a new instance of the Mutator class.
*
* @param originalFileContents Original contents of the file.
*/
public constructor(originalFileContents: string) {
this.originalFileContents = originalFileContents;
}
/**
* Initializes a new instance of the Mutator class.
*
* @param originalFileContents Original contents of the file.
*/
public constructor(originalFileContents: string) {
this.originalFileContents = originalFileContents;
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @param mutatorFactory Creates mutators for mutations.
* @returns File contents after applying the mutation.
*/
public abstract mutate(fileContents: string, mutation: IMutation, mutatorFactory: IMutatorFactory): string;
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @param mutatorFactory Creates mutators for mutations.
* @returns File contents after applying the mutation.
*/
public abstract mutate(
fileContents: string,
mutation: Mutation,
mutatorFactory: MutatorFactory
): string;
/**
* Gets the original contents of the file.
*
* @returns Original contents of the file.
*/
protected getOriginalFileContents(): string {
return this.originalFileContents;
}
/**
* Gets the original contents of the file.
*
* @returns Original contents of the file.
*/
protected getOriginalFileContents(): string {
return this.originalFileContents;
}
}

@@ -1,5 +0,5 @@

import { ILogger } from "./logger";
import { IMutation } from "./mutation";
import { Logger } from "./types/logger";
import { Mutation } from "./types/mutation";
import { Mutator } from "./mutator";
import { IMutatorClass, IMutatorSearcher } from "./mutatorSearcher";
import { MutatorClass, MutatorSearcher } from "./types/mutatorSearcher";

@@ -9,4 +9,4 @@ /**

*/
interface IMutatorClasses {
[i: string]: IMutatorClass<Mutator>;
interface MutatorClasses {
[i: string]: MutatorClass<Mutator>;
}

@@ -17,89 +17,69 @@

*/
export interface IMutatorFactory {
/**
* Attempts to find and instantiate a mutator sub-class for a file.
*
* @param name Dashed-case name of the mutator sub-class.
* @param fileContents Contents of the file.
* @returns An instance of the mutator sub-class, if the sub-class can be found.
*/
generate<TMutator extends Mutator>(name: string, fileContents: string): TMutator | undefined;
export class MutatorFactory {
/**
* Mutator sub-classes, keyed by dashed-case name.
*/
private readonly classes: MutatorClasses = {};
/**
* Generates and applies a mutator, if possible.
*
* @param fileContents Contents of the file.
* @param mutation Mutation to be applied to the file.
* @returns The mutated file contents.
*/
generateAndApply(fileContents: string, mutation: IMutation): string;
}
/**
* Generates output messages for significant operations.
*/
private readonly logger: Logger;
/**
* Creates mutators for mutations.
*/
export class MutatorFactory implements IMutatorFactory {
/**
* Mutator sub-classes, keyed by dashed-case name.
*/
private readonly classes: IMutatorClasses = {};
/**
* Searches for mutator classes.
*/
private readonly searcher: MutatorSearcher;
/**
* Generates output messages for significant operations.
*/
private readonly logger: ILogger;
/**
* Initializes a new instance of the MutatorFactory class.
*
* @param searcher Searches for mutator classes.
*/
public constructor(mutatorSearcher: MutatorSearcher, logger: Logger) {
this.searcher = mutatorSearcher;
this.logger = logger;
}
/**
* Searches for mutator classes.
*/
private readonly searcher: IMutatorSearcher;
/**
* Attempts to find and instantiate a mutator sub-class for a file.
*
* @param name Dashed-case name of the mutator sub-class.
* @param fileContents Contents of the file.
* @returns An instance of the mutator sub-class, if the sub-class can be found.
*/
public generate<TMutator extends Mutator>(
name: string,
fileContents: string
): TMutator | undefined {
if (!this.classes[name]) {
const mutatorClass = this.searcher.search<TMutator>(name);
if (!mutatorClass) {
return undefined;
}
/**
* Initializes a new instance of the MutatorFactory class.
*
* @param searcher Searches for mutator classes.
*/
public constructor(mutatorSearcher: IMutatorSearcher, logger: ILogger) {
this.searcher = mutatorSearcher;
this.logger = logger;
this.classes[name] = mutatorClass;
}
/**
* Attempts to find and instantiate a mutator sub-class for a file.
*
* @param name Dashed-case name of the mutator sub-class.
* @param fileContents Contents of the file.
* @returns An instance of the mutator sub-class, if the sub-class can be found.
*/
public generate<TMutator extends Mutator>(name: string, fileContents: string): TMutator | undefined {
if (!this.classes[name]) {
const mutatorClass: IMutatorClass<TMutator> | undefined = this.searcher.search<TMutator>(name);
if (!mutatorClass) {
return undefined;
}
// @todo Use some form of "implements" keyword when TypeScript supports it
return new this.classes[name](fileContents) as TMutator;
}
this.classes[name] = mutatorClass;
}
/**
* Generates and applied a mutator, if possible.
*
* @param fileContents Contents of the file.
* @param mutation Mutation to be applied to the file.
* @returns The mutated file contents.
*/
public generateAndApply(fileContents: string, mutation: Mutation): string {
const mutator = this.generate(mutation.type, fileContents);
if (!mutator) {
this.logger.onUnknownMutationType(mutation);
// @todo Use some form of "implements" keyword when TypeScript supports it
return new (this.classes[name])(fileContents) as TMutator;
return fileContents;
}
/**
* Generates and applied a mutator, if possible.
*
* @param fileContents Contents of the file.
* @param mutation Mutation to be applied to the file.
* @returns The mutated file contents.
*/
public generateAndApply(fileContents: string, mutation: IMutation): string {
const mutator: Mutator | undefined = this.generate(mutation.type, fileContents);
if (!mutator) {
this.logger.onUnknownMutationType(mutation);
return fileContents;
}
return mutator.mutate(fileContents, mutation, this);
}
return mutator.mutate(fileContents, mutation, this);
}
}

@@ -1,5 +0,8 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";
import { IMutatorFactory } from "../mutatorFactory";
import { orderMutationsFirstToLast, orderMutationsLastToFirst } from "../ordering";
import { MutatorFactory } from "../mutatorFactory";
import {
orderMutationsFirstToLast,
orderMutationsLastToFirst,
} from "../ordering";

@@ -9,12 +12,12 @@ /**

*/
export interface IMultipleMutations extends IMutation {
/**
* Mutations to be applied together.
*/
mutations: IMutation[];
export interface MultipleMutations extends Mutation {
/**
* Mutations to be applied together.
*/
mutations: Mutation[];
/**
* Unique type name identifying multiple mutations.
*/
type: "multiple";
/**
* Unique type name identifying multiple mutations.
*/
type: "multiple";
}

@@ -26,16 +29,23 @@

export class MultipleMutator extends Mutator {
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: IMultipleMutations, mutatorFactory: IMutatorFactory): string {
for (const childMutation of orderMutationsLastToFirst(mutation.mutations)) {
fileContents = mutatorFactory.generateAndApply(fileContents, childMutation);
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(
fileContents: string,
mutation: MultipleMutations,
mutatorFactory: MutatorFactory
): string {
for (const childMutation of orderMutationsLastToFirst(mutation.mutations)) {
fileContents = mutatorFactory.generateAndApply(
fileContents,
childMutation
);
}
return fileContents;
}
return fileContents;
}
}

@@ -50,23 +60,26 @@

*/
export const combineMutations = (...mutations: IMutation[]): IMultipleMutations => {
let begin = mutations[0].range.begin;
let end = mutations[0].range.end;
export const combineMutations = (
...mutations: Mutation[]
): MultipleMutations => {
let begin = mutations[0].range.begin;
let end = mutations[0].range.end;
for (const { range } of mutations) {
begin = Math.min(begin, range.begin);
for (const { range } of mutations) {
begin = Math.min(begin, range.begin);
if (range.end !== undefined && (end === undefined || range.end > end)) {
end = range.end;
}
end = end === undefined
? Math.max(begin, range.begin)
: Math.max(end, begin, range.begin);
if (range.end !== undefined && (end === undefined || range.end > end)) {
end = range.end;
}
return {
mutations: orderMutationsFirstToLast(mutations),
range: { begin, end },
type: "multiple",
};
end =
end === undefined
? Math.max(begin, range.begin)
: Math.max(end, begin, range.begin);
}
return {
mutations: orderMutationsFirstToLast(mutations),
range: { begin, end },
type: "multiple",
};
};

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -7,7 +7,7 @@

*/
export interface ITextDeleteMutation extends IMutation {
/**
* Unique type name identifying text delete mutations.
*/
type: "text-delete";
export interface TextDeleteMutation extends Mutation {
/**
* Unique type name identifying text delete mutations.
*/
type: "text-delete";
}

@@ -19,15 +19,15 @@

export class TextDeleteMutator extends Mutator {
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: ITextDeleteMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: TextDeleteMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
}

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -7,12 +7,12 @@

*/
export interface ITextInsertMutation extends IMutation {
/**
* Text to be inserted.
*/
insertion: string;
export interface TextInsertMutation extends Mutation {
/**
* Text to be inserted.
*/
insertion: string;
/**
* Unique type name identifying text insert mutations.
*/
type: "text-insert";
/**
* Unique type name identifying text insert mutations.
*/
type: "text-insert";
}

@@ -24,16 +24,16 @@

export class TextInsertMutator extends Mutator {
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: ITextInsertMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
mutation.insertion,
fileContents.substring(mutation.range.begin),
].join("");
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: TextInsertMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
mutation.insertion,
fileContents.substring(mutation.range.begin),
].join("");
}
}

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -7,17 +7,17 @@

*/
export interface ITextReplaceMutation extends IMutation {
/**
* String to be inserted.
*/
replace: string;
export interface TextReplaceMutation extends Mutation {
/**
* String to be inserted.
*/
replace: string;
/**
* String to be removed.
*/
search: string;
/**
* String to be removed.
*/
search: string;
/**
* Unique type name identifying text replace mutations.
*/
type: "text-replace";
/**
* Unique type name identifying text replace mutations.
*/
type: "text-replace";
}

@@ -29,12 +29,15 @@

export class TextReplaceMutator extends Mutator {
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: ITextReplaceMutation): string {
return fileContents.replace(new RegExp(mutation.search, "g"), mutation.replace);
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: TextReplaceMutation): string {
return fileContents.replace(
new RegExp(mutation.search, "g"),
mutation.replace
);
}
}

@@ -1,2 +0,2 @@

import { IMutation } from "../mutation";
import { Mutation } from "../types/mutation";
import { Mutator } from "../mutator";

@@ -7,12 +7,12 @@

*/
export interface ITextSwapMutation extends IMutation {
/**
* Text to be inserted.
*/
insertion: string;
export interface TextSwapMutation extends Mutation {
/**
* Text to be inserted.
*/
insertion: string;
/**
* Unique type name identifying text swap mutations.
*/
type: "text-swap";
/**
* Unique type name identifying text swap mutations.
*/
type: "text-swap";
}

@@ -24,16 +24,16 @@

export class TextSwapMutator extends Mutator {
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: ITextSwapMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
mutation.insertion,
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
/**
* Applies a mutation.
*
* @param fileContents Current contents of the file.
* @param mutation Mutation to apply.
* @returns File contents after applying the mutation.
*/
public mutate(fileContents: string, mutation: TextSwapMutation): string {
return [
fileContents.substring(0, mutation.range.begin),
mutation.insertion,
fileContents.substring(mutation.range.end || mutation.range.begin),
].join("");
}
}
/**
* Transforms dashed-case names to cases.
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
export interface INameTransformer {
/**
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
toCamelCase(name: string): string;
export const toCamelCase = (name: string): string => {
const split: string[] = name.split("-");
/**
* Transforms a dashed-case name to PamelCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
toPascalCase(name: string): string;
}
return (
split[0].toLowerCase() +
split
.slice(1)
.map(
(part: string) =>
part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()
)
.join("")
);
};
/**
* Transforms dashed-case names to cases.
* Transforms a dashed-case name to PascalCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
export class NameTransformer implements INameTransformer {
/**
* Transforms a dashed-case name to camelCase.
*
* @param name A dashed-case name.
* @returns The name as camelCase.
*/
public toCamelCase(name: string): string {
const split: string[] = name.split("-");
return split[0].toLowerCase() + split
.slice(1)
.map((part: string): string =>
part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join("");
}
/**
* Transforms a dashed-case name to PamelCase.
*
* @param name A dashed-case name.
* @returns The name as PascalCase.
*/
public toPascalCase(name: string): string {
return name.split("-")
.map((part: string): string =>
part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join("");
}
}
export const toPascalCase = (name: string): string => {
return name
.split("-")
.map(
(part: string) =>
part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()
)
.join("");
};

@@ -1,2 +0,2 @@

import { IMutation } from "./mutation";
import { Mutation } from "./types/mutation";

@@ -9,5 +9,11 @@ /**

*/
export const orderMutationsFirstToLast = (mutations: ReadonlyArray<IMutation>): IMutation[] =>
mutations.slice().sort((a: IMutation, b: IMutation): number =>
(a.range.end || a.range.begin) - (b.range.end || b.range.begin));
export const orderMutationsFirstToLast = (
mutations: ReadonlyArray<Mutation>
): Mutation[] =>
mutations
.slice()
.sort(
(a: Mutation, b: Mutation): number =>
(a.range.end || a.range.begin) - (b.range.end || b.range.begin)
);

@@ -20,5 +26,11 @@ /**

*/
export const orderMutationsLastToFirst = (mutations: ReadonlyArray<IMutation>): IMutation[] =>
mutations.slice().sort((a: IMutation, b: IMutation): number =>
(b.range.end || b.range.begin) - (a.range.end || a.range.begin));
export const orderMutationsLastToFirst = (
mutations: ReadonlyArray<Mutation>
): Mutation[] =>
mutations
.slice()
.sort(
(a: Mutation, b: Mutation): number =>
(b.range.end || b.range.begin) - (a.range.end || a.range.begin)
);

@@ -31,18 +43,20 @@ /**

*/
export const orderMutationsLastToFirstWithoutOverlaps = (mutations: ReadonlyArray<IMutation>): IMutation[] => {
const ordered = orderMutationsFirstToLast(mutations);
const orderedWithoutOverlaps: IMutation[] = [];
let lastStart = Infinity;
export const orderMutationsLastToFirstWithoutOverlaps = (
mutations: ReadonlyArray<Mutation>
): Mutation[] => {
const ordered = orderMutationsFirstToLast(mutations);
const orderedWithoutOverlaps: Mutation[] = [];
let lastStart = Infinity;
for (let i: number = ordered.length - 1; i >= 0; i -= 1) {
const mutation: IMutation = ordered[i];
if ((mutation.range.end || mutation.range.begin) > lastStart) {
continue;
}
lastStart = mutation.range.begin;
orderedWithoutOverlaps.push(mutation);
for (let i: number = ordered.length - 1; i >= 0; i -= 1) {
const mutation: Mutation = ordered[i];
if ((mutation.range.end || mutation.range.begin) > lastStart) {
continue;
}
return orderedWithoutOverlaps;
lastStart = mutation.range.begin;
orderedWithoutOverlaps.push(mutation);
}
return orderedWithoutOverlaps;
};

@@ -1,7 +0,7 @@

import { IAutoMutatorSettings } from "./autoMutator";
import { ILogger } from "./logger";
import { AutoMutatorSettings } from "./autoMutator";
import { Logger } from "./types/logger";
import { ConsoleLogger } from "./loggers/consoleLogger";
import { IMutationsApplier } from "./mutationsApplier";
import { MutationsApplier } from "./types/mutationsApplier";
import { FileMutationsApplier } from "./mutationsAppliers/fileMutationsApplier";
import { IMutationsProvider, IMutationsWave } from "./mutationsProvider";
import { MutationsProvider, MutationsWave } from "./mutationsProvider";

@@ -11,17 +11,17 @@ /**

*/
export interface IMutationRunSettings {
/**
* Generates output messages for significant operations.
*/
logger?: ILogger;
export interface MutationRunSettings {
/**
* Generates output messages for significant operations.
*/
logger?: Logger;
/**
* Applies individual waves of file mutations.
*/
mutationsApplier?: IMutationsApplier;
/**
* Applies individual waves of file mutations.
*/
mutationsApplier?: MutationsApplier;
/**
* Provides waves of file mutations.
*/
mutationsProvider: IMutationsProvider;
/**
* Provides waves of file mutations.
*/
mutationsProvider: MutationsProvider;
}

@@ -32,7 +32,7 @@

*/
export interface IMutationRunResults {
/**
* Names of all files that were mutated at least once.
*/
mutatedFileNames: string[];
export interface MutationRunResults {
/**
* Names of all files that were mutated at least once.
*/
mutatedFileNames: string[];
}

@@ -45,25 +45,29 @@

*/
export const runMutations = async (settings: IAutoMutatorSettings): Promise<IMutationRunResults> => {
const logger = settings.logger || new ConsoleLogger();
const mutatedFileNames = new Set<string>();
const mutationsApplier = settings.mutationsApplier || new FileMutationsApplier({ logger });
export const runMutations = async (
settings: AutoMutatorSettings
): Promise<MutationRunResults> => {
const logger = settings.logger || new ConsoleLogger();
const mutatedFileNames = new Set<string>();
const mutationsApplier =
settings.mutationsApplier || new FileMutationsApplier({ logger });
while (true) {
const mutationsWave: IMutationsWave = await settings.mutationsProvider.provide();
if (mutationsWave.fileMutations === undefined) {
break;
}
while (true) {
const mutationsWave: MutationsWave =
await settings.mutationsProvider.provide();
if (mutationsWave.fileMutations === undefined) {
break;
}
logger.onWaveBegin(mutationsWave);
await mutationsApplier.apply(mutationsWave.fileMutations);
logger.onWaveEnd(mutationsWave);
logger.onWaveBegin(mutationsWave);
await mutationsApplier.apply(mutationsWave.fileMutations);
logger.onWaveEnd(mutationsWave);
for (const fileName of Object.keys(mutationsWave.fileMutations)) {
mutatedFileNames.add(fileName);
}
for (const fileName of Object.keys(mutationsWave.fileMutations)) {
mutatedFileNames.add(fileName);
}
}
return {
mutatedFileNames: Array.from(mutatedFileNames),
};
return {
mutatedFileNames: Array.from(mutatedFileNames),
};
};

@@ -1,2 +0,2 @@

import { ITestCase, ITestDirectories } from "./testCase";
import { TestCase, TestDirectories } from "./testCase";

@@ -7,49 +7,53 @@ /**

export class CaseGrouper {
/**
* Groups test cases into directories.
*
* @param testCases Test cases to be grouped.
* @returns The test cases grouped into directories.
*/
public group(testCases: ITestCase[]): ITestDirectories {
const testDirectories: ITestDirectories = {};
/**
* Groups test cases into directories.
*
* @param testCases Test cases to be grouped.
* @returns The test cases grouped into directories.
*/
public group(testCases: TestCase[]): TestDirectories {
const testDirectories: TestDirectories = {};
for (const testCase of testCases) {
this.addTestCaseToDirectories(testCase, testDirectories);
}
return testDirectories;
for (const testCase of testCases) {
this.addTestCaseToDirectories(testCase, testDirectories);
}
/**
* Adds a test case under its directory, creating directories as needed.
*
* @param testCase A test case to be grouped.
* @param tesDirectories Nested directories of test cases.
*/
private addTestCaseToDirectories(testCase: ITestCase, testDirectories: ITestDirectories): void {
let currentDirectories: ITestDirectories = testDirectories;
return testDirectories;
}
for (let i: number = 0; i < testCase.directoryPath.length - 1; i += 1) {
const directoryName: string = testCase.directoryPath[i];
if (!currentDirectories[directoryName]) {
currentDirectories[directoryName] = {
cases: {},
directories: {}
};
}
/**
* Adds a test case under its directory, creating directories as needed.
*
* @param testCase A test case to be grouped.
* @param tesDirectories Nested directories of test cases.
*/
private addTestCaseToDirectories(
testCase: TestCase,
testDirectories: TestDirectories
) {
let currentDirectories: TestDirectories = testDirectories;
currentDirectories = currentDirectories[directoryName].directories;
}
for (let i: number = 0; i < testCase.directoryPath.length - 1; i += 1) {
const directoryName = testCase.directoryPath[i];
if (!currentDirectories[directoryName]) {
currentDirectories[directoryName] = {
cases: {},
directories: {},
};
}
const lastDirectoryName: string = testCase.directoryPath[testCase.directoryPath.length - 1];
if (!currentDirectories[lastDirectoryName]) {
currentDirectories[lastDirectoryName] = {
cases: {},
directories: {}
};
}
currentDirectories = currentDirectories[directoryName].directories;
}
currentDirectories[lastDirectoryName].cases[testCase.name] = testCase;
const lastDirectoryName =
testCase.directoryPath[testCase.directoryPath.length - 1];
if (!currentDirectories[lastDirectoryName]) {
currentDirectories[lastDirectoryName] = {
cases: {},
directories: {},
};
}
currentDirectories[lastDirectoryName].cases[testCase.name] = testCase;
}
}

@@ -1,11 +0,14 @@

import * as path from "path";
import { StubFileProvider } from "../../src/fileProviders/stubFileProvider";
import { NoopLogger } from "../../src/loggers/noopLogger";
import { FileMutationsApplier } from "../../src/mutationsAppliers/fileMutationsApplier";
import { MutatorFactory } from "../../src/mutatorFactory";
import { TestCase } from "./testCase";
import { IFileProvider } from "../../lib/fileProvider";
import { FileProviderFactory } from "../../lib/fileProviderFactory";
import { StubFileProvider } from "../../lib/fileProviders/stubFileProvider";
import { ILogger, Logger } from "../../lib/logger";
import { IMutationsApplier, MutationsApplier } from "../../lib/mutationsApplier";
import { MutatorFactory } from "../../lib/mutatorFactory";
import { IMutatorSearcher, MutatorSearcher } from "../../lib/mutatorSearcher";
import { ITestCase } from "./testCase";
import { MultipleMutator } from "../../src/mutators/multipleMutator";
import { TextDeleteMutator } from "../../src/mutators/textDeleteMutator";
import { TextInsertMutator } from "../../src/mutators/textInsertMutator";
import { TextReplaceMutator } from "../../src/mutators/textReplaceMutator";
import { TextSwapMutator } from "../../src/mutators/textSwapMutator";
import { MutatorClass } from "../../src/types/mutatorSearcher";
import { Mutator } from "../../src/mutator";

@@ -16,8 +19,18 @@ /**

* @param actual Actual string value.
* @param extpected Expected string value.
* @param expected Expected string value.
*/
export interface IExpect {
(actual: string, expected: string): void;
export interface Expect {
(actual: string, expected: string): void;
}
const stubLogger = new NoopLogger();
const mutatorClasses = new Map<string, MutatorClass>([
["multiple", MultipleMutator],
["text-delete", TextDeleteMutator],
["text-insert", TextInsertMutator],
["text-replace", TextReplaceMutator],
["text-swap", TextSwapMutator],
]);
/**

@@ -27,43 +40,49 @@ * Verifies mutations described by test cases.

export class CaseRunner {
/**
* Directs a test harness to expect two strings to be the same.
*/
private readonly expect: IExpect;
/**
* Directs a test harness to expect two strings to be the same.
*/
private readonly expect: Expect;
/**
* Initializes a new instance of the CaseRunner class.
*
* @param expect Directs a test harness to expect two strings to be the same.
*/
public constructor(expect: IExpect) {
this.expect = expect;
}
/**
* Initializes a new instance of the CaseRunner class.
*
* @param expect Directs a test harness to expect two strings to be the same.
*/
public constructor(expect: Expect) {
this.expect = expect;
}
/**
* Runs a test case to validate its results.
*
* @param testCase Mutation test case to be verified.
* @returns A Promise for the test case completing.
*/
public async runCase(testCase: ITestCase): Promise<void> {
// Arrange
const mutatorSearcher: IMutatorSearcher = new MutatorSearcher([
path.join(__dirname, "../../lib/mutators"),
]);
const stubLogger: ILogger = new Logger();
const stubFileProvider: IFileProvider = new StubFileProvider(testCase.before);
const mutationsApplier: IMutationsApplier = new MutationsApplier({
fileProviderFactory: new FileProviderFactory((): IFileProvider => stubFileProvider),
logger: stubLogger,
mutatorFactory: new MutatorFactory(mutatorSearcher, stubLogger),
});
/**
* Runs a test case to validate its results.
*
* @param testCase Mutation test case to be verified.
* @returns A Promise for the test case completing.
*/
public async runCase(testCase: TestCase): Promise<void> {
// Arrange
const stubFileProvider = new StubFileProvider(testCase.before);
const mutationsApplier = new FileMutationsApplier({
fileProviderFactory: {
generate: () => stubFileProvider,
},
logger: stubLogger,
mutatorFactory: new MutatorFactory(
{
search: <TMutator extends Mutator>(name: string) => {
return mutatorClasses.get(name) as MutatorClass<TMutator>;
},
},
stubLogger
),
});
// Act
const actual: string = await mutationsApplier.applyFileMutations(
[testCase.directoryPath.join("/"), testCase.name].join("/"),
testCase.mutations);
// Act
const actual = await mutationsApplier.applyFileMutations(
[testCase.directoryPath.join("/"), testCase.name].join("/"),
testCase.mutations
);
// Assert
this.expect(actual, testCase.after);
}
// Assert
this.expect(actual, testCase.after);
}
}
import * as fs from "fs";
import * as glob from "glob";
import glob from "glob";
import * as path from "path";
import { IMutation } from "../../lib/mutation";
import { casesRoot, ITestCase, ITestCasePath } from "./testCase";
import { Mutation } from "../../lib/mutation";
import { casesRoot, TestCase, TestCasePath } from "./testCase";
import { getMetaUrlDirname } from "./utils";

@@ -12,88 +13,93 @@ /**

export class CaseSearcher {
/**
* Root directory to search for tests under.
*/
private readonly rootDirectory: string;
/**
* Root directory to search for tests under.
*/
private readonly rootDirectory: string;
/**
* Initializes a new instance of the CaseSearcher class.
*
* @param rootDirectory Root directory to search for tests under.
*/
public constructor(rootDirectory: string) {
this.rootDirectory = rootDirectory;
}
/**
* Initializes a new instance of the CaseSearcher class.
*
* @param rootDirectory Root directory to search for tests under.
*/
public constructor(rootDirectory: string) {
this.rootDirectory = getMetaUrlDirname(rootDirectory);
}
/**
* Searches for test cases under the root directory.
*
* @returns A Promise for test cases under the root directory.
*/
public async search(): Promise<ITestCase[]> {
return await this.readTestCases(await this.findMutationFiles());
}
/**
* Searches for test cases under the root directory.
*
* @returns A Promise for test cases under the root directory.
*/
public async search(): Promise<TestCase[]> {
return await this.readTestCases(await this.findMutationFiles());
}
/**
* Finds full paths of mutation files under the root directory.
*
* @returns A Promise for mutation file paths under the root directory.
*/
private async findMutationFiles(): Promise<string[]> {
return new Promise<string[]>((resolve, reject): void => {
glob(
path.join(this.rootDirectory, "**/mutations.json"),
(error, files): void => {
error ? reject(error) : resolve(files);
});
});
}
/**
* Finds full paths of mutation files under the root directory.
*
* @returns A Promise for mutation file paths under the root directory.
*/
private async findMutationFiles(): Promise<string[]> {
return new Promise<string[]>((resolve, reject): void => {
glob(
path.join(this.rootDirectory, "**/mutations.json"),
(error, files): void => {
error ? reject(error) : resolve(files);
}
);
});
}
/**
* Reads test cases corresponding to mutation file paths.
*
* @param mutationFiles Mutation file paths under the root directory.
* @returns A Promise for test cases corresponding to the mutation file paths.
*/
private async readTestCases(mutationFiles: string[]): Promise<ITestCase[]> {
return Promise.all<ITestCase>(
mutationFiles
.map((mutationFile: string): Promise<ITestCase> => {
return this.readTestCase(path.dirname(mutationFile));
}));
}
/**
* Reads test cases corresponding to mutation file paths.
*
* @param mutationFiles Mutation file paths under the root directory.
* @returns A Promise for test cases corresponding to the mutation file paths.
*/
private async readTestCases(mutationFiles: string[]): Promise<TestCase[]> {
return Promise.all<TestCase>(
mutationFiles.map((mutationFile: string): Promise<TestCase> => {
return this.readTestCase(path.dirname(mutationFile));
})
);
}
/**
* Reads a test case corresponding to a mutation file path.
*
* @param mutationFiles Mutation file path under the root directory.
* @returns A Promise for the test case corresponding to the mutation file path.
*/
private async readTestCase(directory: string): Promise<ITestCase> {
const [after, before, mutations]: [string, string, IMutation[]] = await Promise.all([
this.readFile(path.join(directory, "after.txt")),
this.readFile(path.join(directory, "before.txt")),
this.readFile(path.join(directory, "mutations.json")).then(JSON.parse)
]);
const directorySplit: string[] = directory
.substring(directory.indexOf(casesRoot))
.split(/\\|\//g);
const directoryPath: ITestCasePath = directorySplit.slice(0, directorySplit.length - 1);
const name: string = directorySplit[directorySplit.length - 1];
/**
* Reads a test case corresponding to a mutation file path.
*
* @param mutationFiles Mutation file path under the root directory.
* @returns A Promise for the test case corresponding to the mutation file path.
*/
private async readTestCase(directory: string): Promise<TestCase> {
const [after, before, mutations]: [string, string, Mutation[]] =
await Promise.all([
this.readFile(path.join(directory, "after.txt")),
this.readFile(path.join(directory, "before.txt")),
this.readFile(path.join(directory, "mutations.json")).then(JSON.parse),
]);
const directorySplit: string[] = directory
.substring(directory.indexOf(casesRoot))
.split(/\\|\//g);
const directoryPath: TestCasePath = directorySplit.slice(
0,
directorySplit.length - 1
);
const name = directorySplit[directorySplit.length - 1];
return { after, before, directoryPath, mutations, name };
}
return { after, before, directoryPath, mutations, name };
}
/**
* Reads a file from disk.
*
* @param fileName Name of the file.
* @returns A Promise for the contents of the file.
*/
private readFile(fileName: string): Promise<string> {
return new Promise<string>((resolve, reject): void => {
fs.readFile(fileName, (error, data): void => {
error ? reject(error) : resolve(data.toString());
});
});
}
/**
* Reads a file from disk.
*
* @param fileName Name of the file.
* @returns A Promise for the contents of the file.
*/
private readFile(fileName: string): Promise<string> {
return new Promise<string>((resolve, reject): void => {
fs.readFile(fileName, (error, data): void => {
error ? reject(error) : resolve(data.toString());
});
});
}
}
import { CaseRunner } from "./caseRunner";
import { ITestDirectories, ITestDirectory } from "./testCase";
import { TestDirectories, TestDirectory } from "./testCase";
/**
* Runs a named description of a directory of test cases.
*
*
* @param directoryName Name of the directory.
* @param description Describes the directory.
*/
export interface IDescribeDirectory {
(directoryName: string, description: () => void): void;
export interface DescribeDirectory {
(directoryName: string, description: () => void): void;
}

@@ -16,8 +16,8 @@

* Runs a named description of a test case.
*
*
* @param caseName Name of the test case.
* @param description Describes the test case.
*/
export interface IDescribeCase {
(caseName: string, description: () => void): void;
export interface DescribeCase {
(caseName: string, description: () => void): void;
}

@@ -29,53 +29,59 @@

export class CaseVerifier {
/**
* Runs test cases to validate results.
*/
private readonly caseRunner: CaseRunner;
/**
* Runs test cases to validate results.
*/
private readonly caseRunner: CaseRunner;
/**
* Runs a named description of a directory of test cases.
*/
private readonly describeDirectory: IDescribeDirectory;
/**
* Runs a named description of a directory of test cases.
*/
private readonly describeDirectory: DescribeDirectory;
/**
* Runs a named description of a test case.
*/
private readonly describeCase: IDescribeCase;
/**
* Runs a named description of a test case.
*/
private readonly describeCase: DescribeCase;
/**
* Initializes a new instance of the CaseVerifier class.
*
* @param caseRunner Runs test cases to validate results.
* @param describeDirectory Runs a named description of a directory of test cases.
* @param describeCase Runs a named description of a test case.
*/
public constructor(caseRunner: CaseRunner, describeDirectory: IDescribeDirectory, describeCase: IDescribeCase) {
this.caseRunner = caseRunner;
this.describeDirectory = describeDirectory;
this.describeCase = describeCase;
/**
* Initializes a new instance of the CaseVerifier class.
*
* @param caseRunner Runs test cases to validate results.
* @param describeDirectory Runs a named description of a directory of test cases.
* @param describeCase Runs a named description of a test case.
*/
public constructor(
caseRunner: CaseRunner,
describeDirectory: DescribeDirectory,
describeCase: DescribeCase
) {
this.caseRunner = caseRunner;
this.describeDirectory = describeDirectory;
this.describeCase = describeCase;
}
/**
*
*/
public verifyDirectories(testDirectories: TestDirectories) {
for (const directoryName in testDirectories) {
this.describeDirectory(directoryName, () =>
this.verifyDirectory(testDirectories[directoryName])
);
}
}
/**
*
*/
public verifyDirectories(testDirectories: ITestDirectories): void {
for (const directoryName in testDirectories) {
this.describeDirectory(
directoryName,
(): void => this.verifyDirectory(testDirectories[directoryName]));
}
/**
*
*/
private verifyDirectory(testDirectory: TestDirectory) {
for (const caseName in testDirectory.cases) {
this.describeCase(
caseName,
async (): Promise<void> =>
this.caseRunner.runCase(testDirectory.cases[caseName])
);
}
/**
*
*/
private verifyDirectory(testDirectory: ITestDirectory): void {
for (const caseName in testDirectory.cases) {
this.describeCase(
caseName,
async (): Promise<void> => this.caseRunner.runCase(testDirectory.cases[caseName]));
}
this.verifyDirectories(testDirectory.directories);
}
}
this.verifyDirectories(testDirectory.directories);
}
}
[
{
"mutations": [
{
"insertion": "12",
"range": {
"begin": 2
},
"type": "text-insert"
},
{
"insertion": "34",
"range": {
"begin": 3
},
"type": "text-insert"
}
],
{
"mutations": [
{
"insertion": "12",
"range": {
"begin": 2,
"end": 5
"begin": 2
},
"type": "multiple"
}
"type": "text-insert"
},
{
"insertion": "34",
"range": {
"begin": 3
},
"type": "text-insert"
}
],
"range": {
"begin": 2,
"end": 5
},
"type": "multiple"
}
]
[
{
"mutations": [
{
"range": {
"begin": 2,
"end": 5
},
"type": "text-delete"
}],
{
"mutations": [
{
"range": {
"begin": 2,
"end": 5
"begin": 2,
"end": 5
},
"type": "multiple"
}
"type": "text-delete"
}
],
"range": {
"begin": 2,
"end": 5
},
"type": "multiple"
}
]
[
{
"mutations": [
{
"insertion": "34",
"range": {
"begin": 3
},
"type": "text-insert"
},
{
"insertion": "12",
"range": {
"begin": 2
},
"type": "text-insert"
}
],
{
"mutations": [
{
"insertion": "34",
"range": {
"begin": 2,
"end": 5
"begin": 3
},
"type": "multiple"
}
"type": "text-insert"
},
{
"insertion": "12",
"range": {
"begin": 2
},
"type": "text-insert"
}
],
"range": {
"begin": 2,
"end": 5
},
"type": "multiple"
}
]
[
{
"mutations": [
{
"range": {
"begin": 2,
"end": 3
},
"type": "text-delete"
},
{
"insertion": "12",
"range": {
"begin": 4,
"end": 5
},
"type": "text-swap"
}
],
{
"mutations": [
{
"range": {
"begin": 2,
"end": 5
"begin": 2,
"end": 3
},
"type": "multiple"
}
"type": "text-delete"
},
{
"insertion": "12",
"range": {
"begin": 4,
"end": 5
},
"type": "text-swap"
}
],
"range": {
"begin": 2,
"end": 5
},
"type": "multiple"
}
]
[
{
"range": {
"begin": 2,
"end": 5
},
"type": "text-delete"
}
{
"range": {
"begin": 2,
"end": 5
},
"type": "text-delete"
}
]
[
{
"insertion": "123",
"range": {
"begin": 3
},
"type": "text-insert"
}
{
"insertion": "123",
"range": {
"begin": 3
},
"type": "text-insert"
}
]
[
{
"replace": "1",
"search": "c|d",
"range": {
"begin": 2,
"end": 5
},
"type": "text-replace"
}
{
"replace": "1",
"search": "c|d",
"range": {
"begin": 2,
"end": 5
},
"type": "text-replace"
}
]
[
{
"replace": "12",
"search": "cd",
"range": {
"begin": 2,
"end": 5
},
"type": "text-replace"
}
{
"replace": "12",
"search": "cd",
"range": {
"begin": 2,
"end": 5
},
"type": "text-replace"
}
]
[
{
"insertion": "123",
"range": {
"begin": 2,
"end": 5
},
"type": "text-swap"
}
{
"insertion": "123",
"range": {
"begin": 2,
"end": 5
},
"type": "text-swap"
}
]

@@ -1,2 +0,2 @@

import { IMutation } from "../../lib/mutation";
import { Mutation } from "../../lib/mutation";

@@ -11,3 +11,3 @@ /**

*/
export type ITestCasePath = [typeof casesRoot] | ((typeof casesRoot) | string)[]; // ["mutators", ...string[]]
export type TestCasePath = [typeof casesRoot] | (typeof casesRoot | string)[]; // ["mutators", ...string[]]

@@ -17,4 +17,4 @@ /**

*/
export interface ITestDirectories {
[i: string]: ITestDirectory;
export interface TestDirectories {
[i: string]: TestDirectory;
}

@@ -25,12 +25,12 @@

*/
export interface ITestDirectory {
/**
* Test cases, keyed by case name.
*/
cases: ITestCases;
export interface TestDirectory {
/**
* Test cases, keyed by case name.
*/
cases: TestCases;
/**
* Test case directories, keyed by name.
*/
directories: ITestDirectories;
/**
* Test case directories, keyed by name.
*/
directories: TestDirectories;
}

@@ -41,4 +41,4 @@

*/
export interface ITestCases {
[i: string]: ITestCase;
export interface TestCases {
[i: string]: TestCase;
}

@@ -49,27 +49,27 @@

*/
export interface ITestCase {
/**
* Expected results after mutation.
*/
after: string;
export interface TestCase {
/**
* Expected results after mutation.
*/
after: string;
/**
* Original contents before mutation.
*/
before: string;
/**
* Original contents before mutation.
*/
before: string;
/**
* Directory path to the test case, starting with the root directory name for cases.
*/
directoryPath: ITestCasePath;
/**
* Directory path to the test case, starting with the root directory name for cases.
*/
directoryPath: TestCasePath;
/**
* Mutations to be applied in the test.
*/
mutations: IMutation[];
/**
* Mutations to be applied in the test.
*/
mutations: Mutation[];
/**
* Friendly name of the test case.
*/
name: string;
/**
* Friendly name of the test case.
*/
name: string;
}

@@ -9,38 +9,42 @@ import { CaseSearcher } from "./caseSearcher";

export class TestRunner {
/**
* Finds test cases that should be run.
*/
private readonly caseSearcher: CaseSearcher;
/**
* Finds test cases that should be run.
*/
private readonly caseSearcher: CaseSearcher;
/**
* Groups test cases into directories.
*/
private readonly caseGrouper: CaseGrouper;
/**
* Groups test cases into directories.
*/
private readonly caseGrouper: CaseGrouper;
/**
* Verifies that test cases match actual with expected output.
*/
private readonly caseVerifier: CaseVerifier;
/**
* Verifies that test cases match actual with expected output.
*/
private readonly caseVerifier: CaseVerifier;
/**
* Initializes a new instance of the TestRunner class.
*
* @param caseSearcher Finds test cases that should be run.
*/
public constructor(caseSearcher: CaseSearcher, caseGrouper: CaseGrouper, caseVerifier: CaseVerifier) {
this.caseSearcher = caseSearcher;
this.caseGrouper = caseGrouper;
this.caseVerifier = caseVerifier;
}
/**
* Initializes a new instance of the TestRunner class.
*
* @param caseSearcher Finds test cases that should be run.
*/
public constructor(
caseSearcher: CaseSearcher,
caseGrouper: CaseGrouper,
caseVerifier: CaseVerifier
) {
this.caseSearcher = caseSearcher;
this.caseGrouper = caseGrouper;
this.caseVerifier = caseVerifier;
}
/**
* Searches, groups, and verifies test cases.
*
* @returns A Promise for completing the test cases.
*/
public async run(): Promise<void> {
this.caseVerifier.verifyDirectories(
this.caseGrouper.group(
await this.caseSearcher.search()));
}
/**
* Searches, groups, and verifies test cases.
*
* @returns A Promise for completing the test cases.
*/
public async run(): Promise<void> {
this.caseVerifier.verifyDirectories(
this.caseGrouper.group(await this.caseSearcher.search())
);
}
}
{
"compilerOptions": {
"declaration": true,
"lib": [
"es2015"
],
"module": "commonjs",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "lib",
"pretty": true,
"sourceMap": true,
"strict": true,
"target": "es3"
},
"exclude": [
"node_modules",
"test/**/*"
],
"include": [
"src/**/*"
]
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"lib": ["es2015"],
"module": "esnext",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "lib",
"pretty": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "es2020"
},
"exclude": ["node_modules", "test/**/*"],
"include": ["src", "test"]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc