UNPKG

@gracexwho/model-card-generator

Version:

Tool for generating model cards for Jupyter Notebook.

738 lines 35.8 kB
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 __()); }; })(); var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; (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", "lodash", "./control-flow", "./python-parser", "./set", "./specs", "./symbol-table"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sameLocation = exports.RefSet = exports.SymbolType = exports.ReferenceType = exports.DataflowAnalyzer = void 0; var lodash_1 = require("lodash"); var control_flow_1 = require("./control-flow"); var ast = require("./python-parser"); var set_1 = require("./set"); var specs_1 = require("./specs"); var symbol_table_1 = require("./symbol-table"); var DefUse = /** @class */ (function () { function DefUse(DEFINITION, UPDATE, USE) { if (DEFINITION === void 0) { DEFINITION = new RefSet(); } if (UPDATE === void 0) { UPDATE = new RefSet(); } if (USE === void 0) { USE = new RefSet(); } this.DEFINITION = DEFINITION; this.UPDATE = UPDATE; this.USE = USE; } Object.defineProperty(DefUse.prototype, "defs", { get: function () { return this.DEFINITION.union(this.UPDATE); }, enumerable: false, configurable: true }); Object.defineProperty(DefUse.prototype, "uses", { get: function () { return this.UPDATE.union(this.USE); }, enumerable: false, configurable: true }); DefUse.prototype.union = function (that) { return new DefUse(this.DEFINITION.union(that.DEFINITION), this.UPDATE.union(that.UPDATE), this.USE.union(that.USE)); }; DefUse.prototype.update = function (newRefs) { var GEN_RULES = { USE: [ReferenceType.UPDATE, ReferenceType.DEFINITION], UPDATE: [ReferenceType.DEFINITION], DEFINITION: [] }; var KILL_RULES = { // Which types of references "kill" which other types of references? // In general, the rule of thumb here is, if x depends on y, x kills y, because anything that // depends on x will now depend on y transitively. // If x overwrites y, x also kills y. // The one case where a variable doesn't kill a previous variable is the global configuration, because // it neither depends on initializations or updates, nor clobbers them. DEFINITION: [ReferenceType.DEFINITION, ReferenceType.UPDATE], UPDATE: [ReferenceType.DEFINITION, ReferenceType.UPDATE], USE: [] }; var _loop_1 = function (level) { var genSet = new RefSet(); for (var _i = 0, _a = GEN_RULES[level]; _i < _a.length; _i++) { var genLevel = _a[_i]; genSet = genSet.union(newRefs[genLevel]); } var killSet = this_1[level].filter(function (def) { return genSet.items.some(function (gen) { return gen.name == def.name && KILL_RULES[gen.level].indexOf(def.level) != -1; }); }); this_1[level] = this_1[level].minus(killSet).union(genSet); }; var this_1 = this; for (var _i = 0, _a = Object.keys(ReferenceType); _i < _a.length; _i++) { var level = _a[_i]; _loop_1(level); } }; DefUse.prototype.equals = function (that) { return (this.DEFINITION.equals(that.DEFINITION) && this.UPDATE.equals(that.UPDATE) && this.USE.equals(that.USE)); }; DefUse.prototype.createFlowsFrom = function (fromSet) { var toSet = this; var refsDefined = new RefSet(); var newFlows = new set_1.Set(getDataflowId); for (var _i = 0, _a = Object.keys(ReferenceType); _i < _a.length; _i++) { var level = _a[_i]; for (var _b = 0, _c = toSet[level].items; _b < _c.length; _b++) { var to = _c[_b]; for (var _d = 0, _e = fromSet[level].items; _d < _e.length; _d++) { var from = _e[_d]; if (from.name == to.name) { refsDefined.add(to); newFlows.add({ fromNode: from.node, toNode: to.node, fromRef: from, toRef: to }); } } } } return [newFlows, refsDefined]; }; return DefUse; }()); var defaultDataflowAnalyzerOptions = { symbolTable: { loadDefaultModuleMap: true, moduleMap: {} } }; /** * Use a shared dataflow analyzer object for all dataflow analysis / querying for defs and uses. * It caches defs and uses for each statement, which can save time. * For caching to work, statements must be annotated with a cell's ID and execution count. */ var DataflowAnalyzer = /** @class */ (function () { function DataflowAnalyzer(options) { if (options === void 0) { options = defaultDataflowAnalyzerOptions; } this._defUsesCache = {}; var moduleMap = options.symbolTable.loadDefaultModuleMap ? specs_1.DefaultSpecs : {}; if (options.symbolTable.moduleMap !== undefined) { lodash_1.default.merge(moduleMap, options.symbolTable.moduleMap); } this._symbolTable = new symbol_table_1.SymbolTable(moduleMap); } DataflowAnalyzer.prototype.getDefUseForStatement = function (statement, defsForMethodResolution) { var cacheKey = ast.locationString(statement.location); var cached = this._defUsesCache[cacheKey]; if (cached) { return cached; } var defSet = this.getDefs(statement, defsForMethodResolution); var useSet = this.getUses(statement); var result = new DefUse(defSet.filter(function (r) { return r.level === ReferenceType.DEFINITION; }), defSet.filter(function (r) { return r.level === ReferenceType.UPDATE; }), useSet); this._defUsesCache[cacheKey] = result; return result; }; DataflowAnalyzer.prototype.analyze = function (cfg, refSet) { var workQueue = cfg.blocks.reverse(); var undefinedRefs = new RefSet(); var dataflows = new set_1.Set(getDataflowId); var defUsePerBlock = new Map(workQueue.map(function (block) { return [block.id, new DefUse()]; })); if (refSet) { defUsePerBlock.get(cfg.blocks[0].id).update(new DefUse(refSet)); } while (workQueue.length) { var block = workQueue.pop(); var initialBlockDefUse = defUsePerBlock.get(block.id); var blockDefUse = cfg .getPredecessors(block) .reduce(function (defuse, predBlock) { return defuse.union(defUsePerBlock.get(predBlock.id)); }, initialBlockDefUse); for (var _i = 0, _a = block.statements; _i < _a.length; _i++) { var statement = _a[_i]; var statementDefUse = this.getDefUseForStatement(statement, blockDefUse.defs); var _b = statementDefUse.createFlowsFrom(blockDefUse), newFlows = _b[0], definedRefs = _b[1]; dataflows = dataflows.union(newFlows); undefinedRefs = undefinedRefs .union(statementDefUse.uses) .minus(definedRefs); blockDefUse.update(statementDefUse); } if (!initialBlockDefUse.equals(blockDefUse)) { defUsePerBlock.set(block.id, blockDefUse); // We've updated this block's info, so schedule its successor blocks. for (var _c = 0, _d = cfg.getSuccessors(block); _c < _d.length; _c++) { var succ = _d[_c]; if (workQueue.indexOf(succ) < 0) { workQueue.push(succ); } } } } cfg.visitControlDependencies(function (controlStmt, stmt) { return dataflows.add({ fromNode: controlStmt, toNode: stmt }); }); return { dataflows: dataflows, undefinedRefs: undefinedRefs }; }; DataflowAnalyzer.prototype.getDefs = function (statement, defsForMethodResolution) { if (!statement) return new RefSet(); var defs = runAnalysis(ApiCallAnalysis, defsForMethodResolution, statement, this._symbolTable).union(runAnalysis(DefAnnotationAnalysis, defsForMethodResolution, statement, this._symbolTable)); switch (statement.type) { case ast.IMPORT: defs = defs.union(this.getImportDefs(statement)); break; case ast.FROM: defs = defs.union(this.getImportFromDefs(statement)); break; case ast.DEF: defs = defs.union(this.getFuncDefs(statement, defsForMethodResolution)); break; case ast.CLASS: defs = defs.union(this.getClassDefs(statement)); break; case ast.ASSIGN: defs = defs.union(this.getAssignDefs(statement)); break; } return defs; }; DataflowAnalyzer.prototype.getClassDefs = function (classDecl) { return new RefSet({ type: SymbolType.CLASS, level: ReferenceType.DEFINITION, name: classDecl.name, location: classDecl.location, node: classDecl }); }; DataflowAnalyzer.prototype.getFuncDefs = function (funcDecl, defsForMethodResolution) { runAnalysis(ParameterSideEffectAnalysis, defsForMethodResolution, funcDecl, this._symbolTable); return new RefSet({ type: SymbolType.FUNCTION, level: ReferenceType.DEFINITION, name: funcDecl.name, location: funcDecl.location, node: funcDecl }); }; DataflowAnalyzer.prototype.getAssignDefs = function (assign) { var targetsDefListener = new TargetsDefListener(assign, this._symbolTable); return targetsDefListener.defs; }; DataflowAnalyzer.prototype.getImportFromDefs = function (from) { this._symbolTable.importModuleDefinitions(from.base, from.imports); return new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], from.imports.map(function (i) { return { type: SymbolType.IMPORT, level: ReferenceType.DEFINITION, name: i.name || i.path, location: i.location, node: from }; }))))(); }; DataflowAnalyzer.prototype.getImportDefs = function (imprt) { var _this = this; imprt.names.forEach(function (imp) { var spec = _this._symbolTable.importModule(imp.path, imp.name); }); return new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], imprt.names.map(function (nameNode) { return { type: SymbolType.IMPORT, level: ReferenceType.DEFINITION, name: nameNode.name || nameNode.path, location: nameNode.location, node: imprt }; }))))(); }; DataflowAnalyzer.prototype.getUses = function (statement) { switch (statement.type) { case ast.ASSIGN: return this.getAssignUses(statement); case ast.DEF: return this.getFuncDeclUses(statement); case ast.CLASS: return this.getClassDeclUses(statement); default: { return this.getNameUses(statement); } } }; DataflowAnalyzer.prototype.getNameUses = function (statement) { var usedNames = gatherNames(statement); return new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], usedNames.items.map(function (_a) { var name = _a[0], node = _a[1]; return { type: SymbolType.VARIABLE, level: ReferenceType.USE, name: name, location: node.location, node: statement }; }))))(); }; DataflowAnalyzer.prototype.getClassDeclUses = function (classDecl) { var _this = this; return classDecl.code.reduce(function (uses, classStatement) { return uses.union(_this.getUses(classStatement)); }, new RefSet()); }; DataflowAnalyzer.prototype.getFuncDeclUses = function (def) { var defCfg = new control_flow_1.ControlFlowGraph(def); var undefinedRefs = this.analyze(defCfg, getParameterRefs(def)) .undefinedRefs; return undefinedRefs.filter(function (r) { return r.level == ReferenceType.USE; }); }; DataflowAnalyzer.prototype.getAssignUses = function (assign) { // XXX: Is this supposed to union with funcArgs? var targetNames = gatherNames(assign.targets); var targets = new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], targetNames.items.map(function (_a) { var name = _a[0], node = _a[1]; return { type: SymbolType.VARIABLE, level: ReferenceType.USE, name: name, location: node.location, node: assign }; }))))(); var sourceNames = gatherNames(assign.sources); var sources = new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], sourceNames.items.map(function (_a) { var name = _a[0], node = _a[1]; return { type: SymbolType.VARIABLE, level: ReferenceType.USE, name: name, location: node.location, node: assign }; }))))(); return sources.union(assign.op ? targets : new RefSet()); }; return DataflowAnalyzer; }()); exports.DataflowAnalyzer = DataflowAnalyzer; var ReferenceType; (function (ReferenceType) { ReferenceType["DEFINITION"] = "DEFINITION"; ReferenceType["UPDATE"] = "UPDATE"; ReferenceType["USE"] = "USE"; })(ReferenceType = exports.ReferenceType || (exports.ReferenceType = {})); var SymbolType; (function (SymbolType) { SymbolType[SymbolType["VARIABLE"] = 0] = "VARIABLE"; SymbolType[SymbolType["CLASS"] = 1] = "CLASS"; SymbolType[SymbolType["FUNCTION"] = 2] = "FUNCTION"; SymbolType[SymbolType["IMPORT"] = 3] = "IMPORT"; SymbolType[SymbolType["MUTATION"] = 4] = "MUTATION"; SymbolType[SymbolType["MAGIC"] = 5] = "MAGIC"; })(SymbolType = exports.SymbolType || (exports.SymbolType = {})); var RefSet = /** @class */ (function (_super) { __extends(RefSet, _super); function RefSet() { var items = []; for (var _i = 0; _i < arguments.length; _i++) { items[_i] = arguments[_i]; } return _super.apply(this, __spreadArrays([function (r) { return r.name + r.level + ast.locationString(r.location); }], items)) || this; } return RefSet; }(set_1.Set)); exports.RefSet = RefSet; function sameLocation(loc1, loc2) { return (loc1.first_column === loc2.first_column && loc1.first_line === loc2.first_line && loc1.last_column === loc2.last_column && loc1.last_line === loc2.last_line); } exports.sameLocation = sameLocation; function getNameSetId(_a) { var name = _a[0], node = _a[1]; if (!node.location) console.log("***", node); return name + "@" + ast.locationString(node.location); } var NameSet = /** @class */ (function (_super) { __extends(NameSet, _super); function NameSet() { var items = []; for (var _i = 0; _i < arguments.length; _i++) { items[_i] = arguments[_i]; } return _super.apply(this, __spreadArrays([getNameSetId], items)) || this; } return NameSet; }(set_1.Set)); function gatherNames(node) { var _a; if (Array.isArray(node)) { return (_a = new NameSet()).union.apply(_a, node.map(gatherNames)); } else { return new (NameSet.bind.apply(NameSet, __spreadArrays([void 0], ast .walk(node) .filter(function (e) { return e.type == ast.NAME; }) .map(function (e) { return [e.id, e]; }))))(); } } var AnalysisWalker = /** @class */ (function () { function AnalysisWalker(_statement, symbolTable) { this._statement = _statement; this.symbolTable = symbolTable; this.defs = new RefSet(); } return AnalysisWalker; }()); function runAnalysis(Analysis, defsForMethodResolution, statement, symbolTable) { var walker = new Analysis(statement, symbolTable, defsForMethodResolution); ast.walk(statement, walker); return walker.defs; } /** * Tree walk listener for collecting manual def annotations. */ var DefAnnotationAnalysis = /** @class */ (function (_super) { __extends(DefAnnotationAnalysis, _super); function DefAnnotationAnalysis(statement, symbolTable) { return _super.call(this, statement, symbolTable) || this; } DefAnnotationAnalysis.prototype.onEnterNode = function (node) { if (node.type == ast.LITERAL) { var literal = node; // If this is a string, try to parse a def annotation from it if (typeof literal.value == "string" || literal.value instanceof String) { var string = literal.value; var jsonMatch = string.match(/"defs: (.*)"/); if (jsonMatch && jsonMatch.length >= 2) { var jsonString = jsonMatch[1]; var jsonStringUnescaped = jsonString.replace(/\\"/g, '"'); try { var defSpecs = JSON.parse(jsonStringUnescaped); for (var _i = 0, defSpecs_1 = defSpecs; _i < defSpecs_1.length; _i++) { var defSpec = defSpecs_1[_i]; this.defs.add({ type: SymbolType.MAGIC, level: ReferenceType.DEFINITION, name: defSpec.name, location: { first_line: defSpec.pos[0][0] + node.location.first_line, first_column: defSpec.pos[0][1], last_line: defSpec.pos[1][0] + node.location.first_line, last_column: defSpec.pos[1][1] }, node: this._statement }); } } catch (e) { } } } } }; return DefAnnotationAnalysis; }(AnalysisWalker)); /** * Tree walk listener for collecting names used in function call. */ var ApiCallAnalysis = /** @class */ (function (_super) { __extends(ApiCallAnalysis, _super); function ApiCallAnalysis(statement, symbolTable, variableDefs) { var _this = _super.call(this, statement, symbolTable) || this; _this.variableDefs = variableDefs; return _this; } ApiCallAnalysis.prototype.onEnterNode = function (node, ancestors) { var _this = this; if (node.type !== ast.CALL) { return; } var funcSpec; var func = node.func; if (func.type === ast.DOT && func.value.type === ast.NAME) { // It's a method call or module call. var receiver_1 = func.value; var moduleSpec = this.symbolTable.modules[receiver_1.id]; if (moduleSpec) { // It's a module call. funcSpec = moduleSpec.functions.find(function (f) { return f.name === func.name; }); } else { // It's a method call. var ref = this.variableDefs.items.find(function (r) { return r.name === receiver_1.id; }); if (ref) { // The lefthand side of the dot is a variable we're tracking, so it's a method call. var receiverType = ref.inferredType; if (receiverType) { var funcName_1 = func.name; funcSpec = receiverType.methods.find(function (m) { return m.name === funcName_1; }); } } } } else if (func.type === ast.NAME) { // It's a function call. funcSpec = this.symbolTable.lookupFunction(func.id); } if (funcSpec && funcSpec.updates) { funcSpec.updates.forEach(function (paramName) { var position = typeof paramName === "string" ? parseInt(paramName) : paramName; if (isNaN(position)) { return; } // TODO: think about mutation of global variables var actualArgName; if (0 < position && position - 1 < node.args.length) { var arg = node.args[position - 1].actual; if (arg.type === ast.NAME) { actualArgName = arg.id; } } else if (position === 0 && node.func.type === ast.DOT && node.func.value.type === ast.NAME) { actualArgName = node.func.value.id; } if (actualArgName) { _this.defs.add({ type: SymbolType.MUTATION, level: ReferenceType.UPDATE, name: actualArgName, location: node.location, node: _this._statement }); } }); } else { // Be conservative. If we don't know what the call does, assume that it mutates its arguments. node.args.forEach(function (arg) { if (arg.actual.type === ast.NAME) { var name_1 = arg.actual.id; _this.defs.add({ type: SymbolType.MUTATION, level: ReferenceType.UPDATE, name: name_1, location: node.location, node: _this._statement }); } }); if (node.func.type === ast.DOT && node.func.value.type === ast.NAME) { var name_2 = node.func.value.id; this.defs.add({ type: SymbolType.MUTATION, level: ReferenceType.UPDATE, name: name_2, location: node.location, node: this._statement }); } } }; return ApiCallAnalysis; }(AnalysisWalker)); /** * Tree walk listener for collecting definitions in the target of an assignment. */ var TargetsDefListener = /** @class */ (function (_super) { __extends(TargetsDefListener, _super); function TargetsDefListener(assign, symbolTable) { var _this = _super.call(this, assign, symbolTable) || this; _this.isAugAssign = !!assign.op; if (assign.targets) { for (var _i = 0, _a = assign.targets; _i < _a.length; _i++) { var target = _a[_i]; ast.walk(target, _this); } } assign.sources.forEach(function (source, i) { if (source.type === ast.CALL) { var spec = symbolTable.lookupNode(source.func); var target_1 = assign.targets[i]; if (spec && target_1 && target_1.type === ast.NAME) { var def = _this.defs.items.find(function (d) { return d.name === target_1.id; }); if (def) { def.inferredType = spec.returnsType; } } } }); return _this; } TargetsDefListener.prototype.onEnterNode = function (target, ancestors) { if (target.type == ast.NAME) { if (ancestors.length > 1) { var parent_1 = ancestors[0]; if (parent_1.type === ast.INDEX && parent_1.args.some(function (a) { return a === target; })) { return; // target not defined here. For example, i is not defined in A[i] } } var isUpdate = this.isAugAssign || ancestors.some(function (a) { return a.type == ast.DOT || a.type == ast.INDEX; }); this.defs.add({ type: SymbolType.VARIABLE, level: isUpdate ? ReferenceType.UPDATE : ReferenceType.DEFINITION, location: target.location, name: target.id, node: this._statement }); } }; return TargetsDefListener; }(AnalysisWalker)); var ParameterSideEffectAnalysis = /** @class */ (function (_super) { __extends(ParameterSideEffectAnalysis, _super); function ParameterSideEffectAnalysis(def, symbolTable) { var _this = _super.call(this, def, symbolTable) || this; _this.def = def; var cfg = new control_flow_1.ControlFlowGraph(def); _this.flows = new DataflowAnalyzer().analyze(cfg, getParameterRefs(def)).dataflows; _this.flows = _this.getTransitiveClosure(_this.flows); _this.symbolTable.functions[def.name] = _this.spec = { name: def.name, updates: [] }; return _this; } ParameterSideEffectAnalysis.prototype.getTransitiveClosure = function (flows) { var nodes = flows .map(getNodeId, function (df) { return df.fromNode; }) .union(flows.map(getNodeId, function (df) { return df.toNode; })); var result = new (set_1.Set.bind.apply(set_1.Set, __spreadArrays([void 0, getDataflowId], flows.items)))(); nodes.items.forEach(function (from) { return nodes.items.forEach(function (to) { return nodes.items.forEach(function (middle) { if (flows.has({ fromNode: from, toNode: middle }) && flows.has({ fromNode: middle, toNode: to })) { result.add({ fromNode: from, toNode: to }); } }); }); }); return result; }; ParameterSideEffectAnalysis.prototype.checkParameterFlow = function (sideEffect) { var _this = this; this.def.params.forEach(function (parm, i) { // For a method, the first parameter is self, which we assign 0. The other parameters are numbered from 1. // For a function def, the parameters are numbered from 1. var parmNum = _this.isMethod ? i : i + 1; if (_this.flows.has({ fromNode: parm, toNode: sideEffect }) && _this.spec.updates.indexOf(parmNum) < 0) { _this.spec.updates.push(parmNum); } }); }; ParameterSideEffectAnalysis.prototype.onEnterNode = function (statement, ancestors) { var _this = this; switch (statement.type) { case ast.ASSIGN: for (var _i = 0, _a = statement.targets; _i < _a.length; _i++) { var target = _a[_i]; if (target.type === ast.DOT) { this.checkParameterFlow(statement); } else if (target.type === ast.INDEX) { this.checkParameterFlow(statement); } } break; case ast.CALL: var funcSpec_1 = this.symbolTable.lookupNode(statement.func); var actuals_1 = statement.args.map(function (a) { return a.actual; }); this.def.params.forEach(function (param, i) { // For a method, the first parameter is self, which we assign 0. The other parameters are numbered from 1. // For a function def, the parameters are numbered from 1. var paramNum = _this.isMethod ? i : i + 1; if (funcSpec_1) { // If we have a spec, see if the parameter is passed as an actual that's side-effected. var paramFlows = _this.flows.filter(function (f) { return f.fromNode === param && f.toNode === statement && f.toRef !== undefined; }); var updates_1 = funcSpec_1.updates.filter(function (u) { return typeof u === "number"; }); if (updates_1.length > 0 && !paramFlows.empty && _this.spec.updates.indexOf(paramNum) < 0) { paramFlows.items.forEach(function (pf) { if (updates_1.find(function (i) { return i > 0 && ast .walk(actuals_1[i - 1]) .find(function (a) { return a.type === ast.NAME && a.id === pf.toRef.name; }); })) { _this.spec.updates.push(paramNum); } else if (updates_1.indexOf(0) >= 0 && statement.func.type === ast.DOT && statement.func.value.type === ast.NAME && statement.func.value.id === pf.toRef.name) { _this.spec.updates.push(0); } }); } } else { // No spec, be conservative and assume this parameter is side-effected. _this.spec.updates.push(paramNum); } }); break; } }; return ParameterSideEffectAnalysis; }(AnalysisWalker)); function getParameterRefs(def) { return new (RefSet.bind.apply(RefSet, __spreadArrays([void 0], def.params.map(function (p) { return ({ name: p.name, level: ReferenceType.DEFINITION, type: SymbolType.VARIABLE, location: p.location, node: p }); }))))(); } function getNodeId(node) { return "" + ast.locationString(node.location); } function getDataflowId(df) { if (!df.fromNode.location) { console.log("*** FROM", df.fromNode, df.fromNode.location); } if (!df.toNode.location) { console.log("*** TO", df.toNode, df.toNode.location); } return getNodeId(df.fromNode) + "->" + getNodeId(df.toNode); } }); //# sourceMappingURL=data-flow.js.map