UNPKG

@gracexwho/model-card-generator

Version:

Tool for generating model cards for Jupyter Notebook.

398 lines 19 kB
(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