Skip to content

Commit

Permalink
move a bunch of crap around;
Browse files Browse the repository at this point in the history
  • Loading branch information
bttmly committed Oct 25, 2018
1 parent affd0f6 commit b07f104
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 36 deletions.
51 changes: 37 additions & 14 deletions src/filters/filter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as R from "ramda";
import * as escodegen from "escodegen";

import S from "../mutators/_syntax";
import { MutantLocation } from "../types";
import { LocationFilter } from "../types";

export const isStringRequire = R.allPass([
R.propEq("type", S.CallExpression),
R.pathEq(["callee", "name"], "require"),
Expand All @@ -16,24 +16,47 @@ export const isUseStrict = R.allPass([
R.pathEq(["expression", "value"], "use strict"),
]);

export const isCallOfName = (name: string) =>
R.allPass([
R.pathEq(["expression", "callee", "type"], "Identifier"),
R.pathEq(["expression", "callee", "name"], name),
]);
export const isCallOfName = (name: string): LocationFilter => {
return ({ node }) => {
if (!R.propEq("type", S.CallExpression, node)) return false;
if (!R.pathEq(["callee", "type"], S.Identifier, node)) return false;
if (!R.pathEq(["callee", "name"], name, node)) return false;
return true;
};
};

export function nodeSourceIncludes(text: string): LocationFilter {
return ({ node }) => {
return escodegen.generate(node).includes(text);
};
}

export function sourceNodeIncludesAny(texts: string[]): LocationFilter {
return ({ node }) => {
const code = escodegen.generate(node);
return texts.some(t => code.includes(t));
};
}

export function nodeSourceMatches(re: RegExp): LocationFilter {
return ({ node }) => {
return re.test(escodegen.generate(node));
};
}

export function nodeSourceIncludesText(text: string) {
return (m: MutantLocation): boolean => {
return escodegen.generate(m.node).includes(text);
export function sourceNodeMatchesAny(res: RegExp[]): LocationFilter {
return ({ node }) => {
const code = escodegen.generate(node);
return res.some(re => re.test(code));
};
}

export function nodeSourceMatches(re: RegExp) {
return (m: MutantLocation): boolean => {
return re.test(escodegen.generate(m.node));
export function sourceNodeIs(text: string): LocationFilter {
return ({ node }) => {
return escodegen.generate(node).trim() === text;
};
}

export const isESModuleInterop = nodeSourceIncludesText(
export const isESModuleInterop = nodeSourceIncludes(
"Object.defineProperty(exports, '__esModule', { value: true });",
);
50 changes: 48 additions & 2 deletions src/mutators/_filters.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
export function hasProp(prop: string) {
return (node: any) => node.hasOwnProperty(prop) && node[prop] !== null;
import * as R from "ramda";
import { NodeFilter } from "../types";
import * as escodegen from "escodegen";
import S from "../mutators/_syntax";

export function hasProp(prop: string): NodeFilter {
return node => {
return R.prop(prop, node as any) != null;
};
}

export const isCallOfName = (name: string): NodeFilter => {
return node => {
if (!R.propEq("type", S.CallExpression, node)) return false;
if (!R.pathEq(["callee", "type"], S.Identifier, node)) return false;
if (!R.pathEq(["callee", "name"], name, node)) return false;
return true;
};
};

export function nodeSourceIncludes(text: string): NodeFilter {
return node => escodegen.generate(node).includes(text);
}

export function sourceNodeIncludesAny(texts: string[]): NodeFilter {
return node => {
const code = escodegen.generate(node);
return texts.some(t => code.includes(t));
};
}

export function nodeSourceMatches(re: RegExp): NodeFilter {
return node => re.test(escodegen.generate(node));
}

export function sourceNodeMatchesAny(res: RegExp[]): NodeFilter {
return node => {
const code = escodegen.generate(node);
return res.some(re => re.test(code));
};
}

export function sourceNodeIs(text: string): NodeFilter {
return node => escodegen.generate(node).trim() === text;
}

export const isESModuleInterop = nodeSourceIncludes(
"Object.defineProperty(exports, '__esModule', { value: true });",
);
6 changes: 6 additions & 0 deletions src/mutators/_syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ S.LOOP_NODES = [S.WhileStatement, S.DoWhileStatement, S.ForStatement];

S.TEST_NODES = [S.IfStatement, S.ConditionalExpression, S.SwitchCase];

S.FUNC_NODES = [
S.FunctionDeclaration,
S.FunctionExpression,
S.ArrowFunctionExpression,
];

export default S;
24 changes: 24 additions & 0 deletions src/mutators/drop-parameter-default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as R from "ramda";
import S from "./_syntax";
import { VOID_NODE } from "./_constant-nodes";
import { MutatorPlugin } from "../types";

// a default parameter
// input: `function fn (x = 1) {}`
// output: `function fn (x) {}`

const plugin: MutatorPlugin = {
type: "mutator",
name: "drop-return",
nodeTypes: S.FUNC_NODES,
mutator: R.ifElse(
node => node.argument == null,
R.always(VOID_NODE),
node => ({
type: S.ExpressionStatement,
expression: node.argument,
}),
),
};

export default plugin;
19 changes: 16 additions & 3 deletions src/mutators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ function makeMutatorIndex(names: string[]): MutatorIndex {
return index;
}

function locateMutatorPlugins(names: string[]): MutatorPlugin[] {
return names.map((name: string): MutatorPlugin => {
function locateMutatorPlugins(
names: string[],
strict = false,
): MutatorPlugin[] {
const plugins = names.map((name: string) => {
try {
const plugin: MutatorPlugin = require(`perturb-plugin-mutator-${name}`);
return plugin;
Expand All @@ -95,9 +98,19 @@ function locateMutatorPlugins(names: string[]): MutatorPlugin[] {
console.log(
`unable to locate -MUTATOR- plugin "${name}" -- fatal error, exiting`,
);
throw err;
if (strict) throw err;
}
});

return removeNils<MutatorPlugin>(plugins);
}

function removeNils<T>(arr: Array<T | null | void>): T[] {
const xs: T[] = [];
for (const item of arr) {
if (item != null) xs.push(item);
}
return xs;
}

// exports.injectPlugins = function (names: string[]) {
Expand Down
8 changes: 1 addition & 7 deletions src/mutators/reverse-function-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@ import S from "./_syntax";
import * as util from "./util";
import { MutatorPlugin } from "../types";

const FUNC_NODES = [
S.FunctionDeclaration,
S.FunctionExpression,
S.ArrowFunctionExpression,
];

// reverse the perameter order for a function expression or declaration
// `function fn (a, b) {}` => `function fn (b, a) {}`
const plugin: MutatorPlugin = {
type: "mutator",
name: "reverse-function-parameters",
nodeTypes: FUNC_NODES,
nodeTypes: S.FUNC_NODES,
filter: util.lengthAtPropGreaterThan("params", 1),
mutator: util.update("params", (ps: any[]) => ps.slice().reverse()),
};
Expand Down
36 changes: 28 additions & 8 deletions src/mutators/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,35 @@ export const update = R.curry(
// return updater(R.prop, obj).map(updated => R.assoc(prop, updated, obj));
// });

export const lengthAtPropGreaterThan = R.curry(
(prop: string, count: number, obj: object): boolean => {
return (R.path([prop, "length"], obj) as number) > count;
},
);
export const lengthAtPropGreaterThan = (prop: string, count: number) => {
return (obj: any) => (R.path([prop, "length"], obj) as number) > count;
};

// given an object with an array property, return an array of
// copies of that object, each copy having one of the array's
// elements removed

// dropEachOfProp("key", {key: [1, 2, 3]})
// => [ {key: [2, 3]}, {key: [1, 3]}, {key: [1, 2]} ]

export const dropEachOfProp = R.curry((prop: string, obj: any): any[] => {
const target: any[] = R.prop(prop, obj);
return target.map((_: any, i: number) => {
return R.assoc(prop, R.remove(i, 1, target), obj);
const arr: any[] = R.prop(prop, obj);
// TODO: runtime verify Array.isArray(arr)
return arr.map((_: any, i: number) => {
return R.assoc(prop, R.remove(i, 1, arr), obj);
});
});

export const dropEachOfPropPred = (prop: string, pred: (x: any) => boolean) => {
return (obj: any) => {
const results: any[] = [];
const arr: any[] = R.prop(prop, obj);
// TODO: runtime verify Array.isArray(target)
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (pred(item)) {
results.push(R.assoc(prop, R.remove(i, 1, arr), obj));
}
}
};
};
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type AggregateReporter = (

export type NodeMutator = (n: ESTree.Node) => ESTree.Node | ESTree.Node[];
export type NodeFilter = (n: ESTree.Node) => boolean;
export type LocationFilter = (l: MutantLocation) => boolean;

export type Skipper = (node: ESTree.Node, path: string[]) => boolean;

Expand Down
1 change: 0 additions & 1 deletion src/util/drop-each-of-prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as R from "ramda";
// dropEachOfProp("key", {key: [1, 2, 3]})
// => [ {key: [2, 3]}, {key: [1, 3]}, {key: [1, 2]} ]

// will be helpful in testing removing each item from an object's property

export default R.curry(function dropEachOfProp(key: any, obj: any) {
return obj[key].map((_: any, i: number) => {
Expand Down
19 changes: 18 additions & 1 deletion test/filters/filter.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const filters = require("../../lib/filters/filter");
const esprima = require("esprima");
const expect = require("expect");
const R = require("ramda");

const esModuleInterop = "Object.defineProperty(exports, '__esModule', { value: true });"

const callFn = "fn(1, 2, 3);"

describe("filter", () => {

describe("#nodeSourceMatchesText", () => {
Expand All @@ -13,16 +16,30 @@ describe("filter", () => {
})
});

describe("#isCallOfname", () => {
it("works", () => {
const loc = parseToLocation(callFn);
offsetLocation(loc, [ "expression" ])
expect(filters.isCallOfName("fn")(loc)).toBe(true);
expect(filters.isCallOfName("fun")(loc)).toBe(false);
})
});

})

function parseToLocation (text) {
const program = esprima.parseModule(text);
if (program.body.length === 0) throw new Error(`Did not create program with zero body statements: ${text}`)
if (program.body.length === 0) throw new Error(`Created program withmore than one body statement: ${text}`)
return {
mutator: null, // TODO: if we start using filters that check the mutatot
mutator: null, // TODO: if we start using filters that check the mutator
path: [],
node: program.body[0],
}
}

// helper to descend into the node to be on the right top level node for a test
function offsetLocation (loc, path) {
loc.node = R.path(path, loc.node);
return loc;
}

0 comments on commit b07f104

Please sign in to comment.