@gracexwho/model-card-generator
Version:
Tool for generating model cards for Jupyter Notebook.
398 lines • 19 kB
JavaScript
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../control-flow", "../data-flow", "../printNode", "../python-parser", "../specs"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var control_flow_1 = require("../control-flow");
var data_flow_1 = require("../data-flow");
var printNode_1 = require("../printNode");
var python_parser_1 = require("../python-parser");
var specs_1 = require("../specs");
describe("detects dataflow dependencies", function () {
function analyze() {
var codeLines = [];
for (var _i = 0; _i < arguments.length; _i++) {
codeLines[_i] = arguments[_i];
}
var code = codeLines.concat("").join("\n"); // add newlines to end of every line.
var analyzer = new data_flow_1.DataflowAnalyzer();
printNode_1.printNode;
return analyzer.analyze(new control_flow_1.ControlFlowGraph(python_parser_1.parse(code))).dataflows;
}
function analyzeLineDeps() {
var codeLines = [];
for (var _i = 0; _i < arguments.length; _i++) {
codeLines[_i] = arguments[_i];
}
return analyze.apply(void 0, codeLines).items.map(function (dep) { return [
dep.toNode.location.first_line,
dep.fromNode.location.first_line
]; });
}
it("from variable uses to names", function () {
var deps = analyzeLineDeps("a = 1", "b = a");
expect(deps).toContainEqual([2, 1]);
});
it("handles multiple statements per line", function () {
var deps = analyzeLineDeps("a = 1", "b = a; c = b", "d = c");
expect(deps).toContainEqual([2, 1]);
expect(deps).toContainEqual([3, 2]);
});
it("only links from a use to its most recent def", function () {
var deps = analyzeLineDeps("a = 2", "a.prop = 3", "a = 4", "b = a");
expect(deps).toContainEqual([4, 3]);
expect(deps).not.toContainEqual([4, 1]);
});
it("handles augmenting assignment", function () {
var deps = analyzeLineDeps("a = 2", "a += 3");
expect(deps).toContainEqual([2, 1]);
});
it("links between statements, not symbol locations", function () {
var deps = analyze("a = 1", "b = a");
expect(deps.items[0].fromNode.location).toEqual({
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 5
});
expect(deps.items[0].toNode.location).toEqual({
first_line: 2,
first_column: 0,
last_line: 2,
last_column: 5
});
});
it("links to a multi-line dependency", function () {
var deps = analyze("a = func(", " 1)", "b = a");
expect(deps.items[0].fromNode.location).toEqual({
first_line: 1,
first_column: 0,
last_line: 2,
last_column: 6
});
});
it("to a full for-loop declaration", function () {
var deps = analyze("for i in range(a, b):", " print(i)");
expect(deps.items[0].fromNode.location).toEqual({
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 21
});
});
it("links from a class use to its def", function () {
var deps = analyzeLineDeps("class C(object):", " pass", "", "c = C()");
expect(deps).toEqual([[4, 1]]);
});
it("links from a function use to its def", function () {
var deps = analyzeLineDeps("def func():", " pass", "", "func()");
expect(deps).toEqual([[4, 1]]);
});
});
describe("detects control dependencies", function () {
function analyze() {
var codeLines = [];
for (var _i = 0; _i < arguments.length; _i++) {
codeLines[_i] = arguments[_i];
}
var code = codeLines.concat("").join("\n"); // add newlines to end of every line.
var deps = [];
new control_flow_1.ControlFlowGraph(python_parser_1.parse(code)).visitControlDependencies(function (control, stmt) {
return deps.push([stmt.location.first_line, control.location.first_line]);
});
return deps;
}
it("to an if-statement", function () {
var deps = analyze("if cond:", " print(a)");
expect(deps).toEqual([[2, 1]]);
});
it("for multiple statements in a block", function () {
var deps = analyze("if cond:", " print(a)", " print(b)");
expect(deps).toEqual([[2, 1], [3, 1]]);
});
it("from an else to an if", function () {
var deps = analyze("if cond:", " print(a)", "elif cond2:", " print(b)", "else:", " print(b)");
expect(deps).toContainEqual([3, 1]);
expect(deps).toContainEqual([5, 3]);
});
it("not from a join to an if-condition", function () {
var deps = analyze("if cond:", " print(a)", "print(b)");
expect(deps).toEqual([[2, 1]]);
});
it("not from a join to a for-loop", function () {
var deps = analyze("for i in range(10):", " print(a)", "print(b)");
expect(deps).toEqual([[2, 1]]);
});
it("to a for-loop", function () {
var deps = analyze("for i in range(10):", " print(a)");
expect(deps).toContainEqual([2, 1]);
});
it("skipping non-dependencies", function () {
var deps = analyze("a = 1", "b = 2");
expect(deps).toEqual([]);
});
});
describe("getDefs", function () {
function getDefsFromStatements(moduleMap) {
var codeLines = [];
for (var _i = 1; _i < arguments.length; _i++) {
codeLines[_i - 1] = arguments[_i];
}
var code = codeLines.concat("").join("\n");
var module = python_parser_1.parse(code);
var options = createDataflowOptionsForModuleMap(moduleMap);
var analyzer = new data_flow_1.DataflowAnalyzer(options);
return module.code.reduce(function (refSet, stmt) {
var refs = analyzer.getDefs(stmt, refSet);
return refSet.union(refs);
}, new data_flow_1.RefSet()).items;
}
function getDefsFromStatement(code, mmap) {
mmap = mmap || specs_1.DefaultSpecs;
code = code + "\n"; // programs need to end with newline
var mod = python_parser_1.parse(code);
var options = createDataflowOptionsForModuleMap(mmap);
var analyzer = new data_flow_1.DataflowAnalyzer(options);
return analyzer.getDefs(mod.code[0], new data_flow_1.RefSet()).items;
}
function getDefNamesFromStatement(code, mmap) {
return getDefsFromStatement(code, mmap).map(function (def) { return def.name; });
}
function createDataflowOptionsForModuleMap(moduleMap) {
var options;
if (moduleMap) {
options = {
symbolTable: {
loadDefaultModuleMap: false,
moduleMap: moduleMap
}
};
}
return options;
}
describe("detects definitions", function () {
it("for assignments", function () {
var defs = getDefsFromStatement("a = 1");
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.VARIABLE,
name: "a",
level: data_flow_1.ReferenceType.DEFINITION
});
});
it("for augmenting assignments", function () {
var defs = getDefsFromStatement("a += 1");
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.VARIABLE,
name: "a",
level: data_flow_1.ReferenceType.UPDATE
});
});
it("for imports", function () {
var defs = getDefsFromStatement("import pandas");
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.IMPORT,
name: "pandas"
});
});
it("for from-imports", function () {
var defs = getDefsFromStatement("from pandas import load_csv");
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.IMPORT,
name: "load_csv"
});
});
it("for function declarations", function () {
var defs = getDefsFromStatement(["def func():", " return 0"].join("\n"));
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.FUNCTION,
name: "func",
location: {
first_line: 1,
first_column: 0,
last_line: 4,
last_column: -1
}
});
});
it("for class declarations", function () {
var defs = getDefsFromStatement(["class C(object):", " def __init__(self):", " pass"].join("\n"));
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.CLASS,
name: "C",
location: {
first_line: 1,
first_column: 0,
last_line: 5,
last_column: -1
}
});
});
describe("that are weak (marked as updates)", function () {
it("for dictionary assignments", function () {
var defs = getDefsFromStatement(["d['a'] = 1"].join("\n"));
expect(defs.length).toBe(1);
expect(defs[0].level).toBe(data_flow_1.ReferenceType.UPDATE);
expect(defs[0].name).toBe("d");
});
it("for property assignments", function () {
var defs = getDefsFromStatement(["obj.a = 1"].join("\n"));
expect(defs.length).toBe(1);
expect(defs[0].level).toBe(data_flow_1.ReferenceType.UPDATE);
expect(defs[0].name).toBe("obj");
});
});
describe("from annotations", function () {
it("from our def annotations", function () {
var defs = getDefsFromStatement('"""defs: [{ "name": "a", "pos": [[0, 0], [0, 11]] }]"""%some_magic');
expect(defs[0]).toMatchObject({
type: data_flow_1.SymbolType.MAGIC,
name: "a",
location: {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 11
}
});
});
it("computing the def location relative to the line it appears on", function () {
var defs = getDefsFromStatements(undefined, "# this is an empty line", '"""defs: [{ "name": "a", "pos": [[0, 0], [0, 11]] }]"""%some_magic');
expect(defs[0]).toMatchObject({
location: {
first_line: 2,
first_column: 0,
last_line: 2,
last_column: 11
}
});
});
});
describe("including", function () {
it("function arguments", function () {
var defs = getDefNamesFromStatement("func(a)");
expect(defs.length).toBe(1);
});
it("the object a function is called on", function () {
var defs = getDefNamesFromStatement("obj.func()");
expect(defs.length).toBe(1);
});
});
describe("; given a spec,", function () {
it("can ignore all arguments", function () {
var defs = getDefsFromStatement("func(a, b, c)", {
__builtins__: { functions: ["func"] }
});
expect(defs).toEqual([]);
});
it("assumes arguments have side-effects, without a spec", function () {
var defs = getDefsFromStatement("func(a, b, c)", {
__builtins__: { functions: [] }
});
expect(defs).not.toBeUndefined();
expect(defs.length).toBe(3);
var names = defs.map(function (d) { return d.name; });
expect(names).toContain("a");
expect(names).toContain("b");
expect(names).toContain("c");
});
it("can ignore the method receiver", function () {
var specs = { __builtins__: { types: { C: { methods: ["m"] } } } };
var defs = getDefsFromStatements(specs, "x=C()", "x.m()");
expect(defs).not.toBeUndefined();
expect(defs.length).toBe(1);
expect(defs[0].name).toContain("x");
expect(defs[0].level).toContain(data_flow_1.ReferenceType.DEFINITION);
});
it("assumes method call affects the receiver, without a spec", function () {
var specs = { __builtins__: {} };
var defs = getDefsFromStatements(specs, "x=C()", "x.m()");
expect(defs).not.toBeUndefined();
expect(defs.length).toBe(2);
expect(defs[1].name).toBe("x");
expect(defs[1].level).toBe(data_flow_1.ReferenceType.UPDATE);
});
it("can process a class name as both a type and function", function () {
var CType = { methods: [{ name: "m", reads: [], updates: [0] }] };
var specs = {
__builtins__: {
types: { C: CType },
functions: [{ name: "C", returns: "C" }]
}
};
var defs = getDefsFromStatements(specs, "x=C()");
expect(defs).not.toBeUndefined();
expect(defs.length).toBe(1);
expect(defs[0].name).toBe("x");
expect(defs[0].inferredType).toEqual(CType);
});
});
});
describe("doesn't detect definitions", function () {
it("for names used outside a function call", function () {
var defs = getDefNamesFromStatement("a + func()");
expect(defs).toEqual([]);
});
it("for functions called early in a call chain", function () {
var defs = getDefNamesFromStatement("func().func()");
expect(defs).toEqual([]);
});
});
});
describe("getUses", function () {
function getUseNames() {
var codeLines = [];
for (var _i = 0; _i < arguments.length; _i++) {
codeLines[_i] = arguments[_i];
}
var code = codeLines.concat("").join("\n");
var mod = python_parser_1.parse(code);
var analyzer = new data_flow_1.DataflowAnalyzer();
return analyzer.getUses(mod.code[0]).items.map(function (use) { return use.name; });
}
describe("detects uses", function () {
it("of functions", function () {
var uses = getUseNames("func()");
expect(uses).toContain("func");
});
it("for undefined symbols in functions", function () {
var uses = getUseNames("def func(arg):", " print(a)");
expect(uses).toContain("a");
});
it("handles augassign", function () {
var uses = getUseNames("x -= 1");
expect(uses).toContain("x");
});
it("of functions inside classes", function () {
var uses = getUseNames("class Baz():", " def quux(self):", " func()");
expect(uses).toContain("func");
});
it("of variables inside classes", function () {
var uses = getUseNames("class Baz():", " def quux(self):", " self.data = a");
expect(uses).toContain("a");
});
it("of functions and variables inside nested classes", function () {
var uses = getUseNames("class Bar():", " class Baz():", " class Qux():", " def quux(self):", " func()", " self.data = a");
expect(uses).toContain("func");
expect(uses).toContain("a");
});
});
describe("ignores uses", function () {
it("for symbols defined within functions", function () {
var uses = getUseNames("def func(arg):", " print(arg)", " var = 1", " print(var)");
expect(uses).not.toContain("arg");
expect(uses).not.toContain("var");
});
it("for params used in an instance function body", function () {
var uses = getUseNames("class Foo():", " def func(arg1):", " print(arg1)");
expect(uses).not.toContain("arg1");
});
});
});
});
//# sourceMappingURL=analysis.test.js.map