From 976af87e469006bb0ec33572f26abec9c898218e Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 16 Mar 2020 22:35:21 -0400 Subject: [PATCH] Refine Trace to be able to output graphviz-compatible data. --- src/core/Form.ts | 2 +- src/core/Line.ts | 4 +-- src/core/Trace.test.ts | 77 ++++++++++++++++++++++++++++++++++++++++++ src/core/Trace.ts | 41 +++++++++++++--------- 4 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 src/core/Trace.test.ts diff --git a/src/core/Form.ts b/src/core/Form.ts index 6515753..9b46ab3 100644 --- a/src/core/Form.ts +++ b/src/core/Form.ts @@ -54,7 +54,7 @@ export default abstract class Form }, if (!(name in this._input)) { throw new NotFoundError(`No input with key ${name} on form ${this.name}`); } - Trace.add(`${this.name}-input-${name}`); + Trace.add(`${this.name} input: ${name}`); return this._input[name]; } diff --git a/src/core/Line.ts b/src/core/Line.ts index ca5dd00..b473130 100644 --- a/src/core/Line.ts +++ b/src/core/Line.ts @@ -58,7 +58,7 @@ export class ReferenceLine, // the one the Line is in, erase |form|'s type with |as any| to // keep TypeScript happy. constructor(form: FormClass, line: L, description?: string, fallback?: T) { - super(description || `Reference F${form.name}.L${line}`); + super(description || `Reference ${form.name}-${line}`); this._form = form; this._line = line; this._fallback = fallback; @@ -108,7 +108,7 @@ export class AccumulatorLine, private _line: L; constructor(form: FormClass, line: L, description?: string) { - super(description || `Accumulator F${form.name}.L${line}`); + super(description || `Accumulator ${form.name}-${line}`); this._form = form; this._line = line; } diff --git a/src/core/Trace.test.ts b/src/core/Trace.test.ts new file mode 100644 index 0000000..342e168 --- /dev/null +++ b/src/core/Trace.test.ts @@ -0,0 +1,77 @@ +// Copyright 2020 Blue Static +// This program is free software licensed under the GNU General Public License, +// version 3.0. The full text of the license can be found in LICENSE.txt. +// SPDX-License-Identifier: GPL-3.0-only + +import Form from './Form'; +import TaxReturn from './TaxReturn'; +import { ComputedLine, InputLine, ReferenceLine } from './Line'; +import Trace, { Edge, getLastTraceList } from './Trace'; + +class TestTaxReturn extends TaxReturn { + get year() { return 2019; } + + get includeJointPersonForms() { return false; } +}; + +interface Input { + name: string; + value: number; +}; + +class TestForm extends Form { + readonly name = 'TF'; + + readonly _lines = { + 'i1': new InputLine('name'), + 'i2': new InputLine('value'), + 'c1': new ComputedLine((tr): string => { + return `Hello ${this.getInput('name')}`; + }), + 'c2': new ComputedLine((tr): number => { + return this.getValue(tr, 'i2') * 0.20; + }), + 'r2': new ReferenceLine(TestForm as any, 'c2'), + }; +}; + +describe('tracing', () => { + const tr = new TestTaxReturn(); + const f = new TestForm({ + name: 'ABC', + value: 100 + }); + tr.addForm(f); + + test('input line', () => { + f.getValue(tr, 'i1'); + const trace = getLastTraceList(); + expect(trace).toStrictEqual([ [ 'TF-i1 (Input from name)', 'TF input: name' ] ]); + }); + + test('computed line via input', () => { + f.getValue(tr, 'c1'); + const trace = getLastTraceList(); + expect(trace).toStrictEqual([ [ 'TF-c1', 'TF input: name' ] ]); + }); + + test('computed line via input line', () => { + f.getValue(tr, 'c2'); + const trace = getLastTraceList(); + expect(trace).toStrictEqual([ + [ 'TF-c2', 'TF-i2 (Input from value)' ], + [ 'TF-i2 (Input from value)', 'TF input: value' ] + ]); + }); + + test('reference line', () => { + f.getValue(tr, 'r2'); + const trace = getLastTraceList(); + expect(trace).toStrictEqual([ + [ 'TF-r2 (Reference TestForm-c2)', 'TF-c2' ], + [ 'TF-c2', 'TF-i2 (Input from value)' ], + [ 'TF-i2 (Input from value)', 'TF input: value' ] + ]); + }); +}); + diff --git a/src/core/Trace.ts b/src/core/Trace.ts index b94fac8..5db974a 100644 --- a/src/core/Trace.ts +++ b/src/core/Trace.ts @@ -9,52 +9,61 @@ var current: Trace = null; var traces: Trace[] = []; +export type Edge = [string, string]; + export default class Trace { - private _stack: Set[] = []; - private _index = 0; + private _edges: { [key: string]: Edge } = {}; + private _stack: string[] = []; private _name: string; constructor(line: Line) { this._name = this._formatLine(line); - const s = new Set(); - - s.add(`Start: ${this._name}`); - if (current === null) { + if (current === null) current = this; - } else { - ++current._index; + + if (current._stack.length != 0) { + current._addEdge([ current._previousEdge(), this._name ]); } - current._stack.push(s); + current._stack.push(this._name); } static add(id: string) { if (current === null) return; - current._stack[current._index].add(id); + current._addEdge([ current._previousEdge(), id ]); } end() { - --current._index; + current._stack.pop(); if (current === this) { current = null; traces.push(this); } } - get traceList(): readonly string[][] { - return this._stack.map(s => [...s.values()]); + get traceList(): readonly Edge[] { + return Object.values(this._edges); + } + + private _addEdge(e: Edge) { + this._edges[`${e[0]}|${e[1]}`] = e; + } + + private _previousEdge(): string { + return this._stack[this._stack.length - 1]; } private _formatLine(line: Line): string { + const description = line.description ? ` (${line.description})` : ''; if (line.form === undefined) - return `${line.constructor.name} (${line.description})`; - return `${line.form.name}-${line.id} (${line.description})`; + return `${line.constructor.name}${description}`; + return `${line.form.name}-${line.id}${description}`; } }; -export function getLastTraceList(): readonly string[][] { +export function getLastTraceList(): readonly Edge[] { if (traces.length == 0) return null; return traces[traces.length - 1].traceList; -- 2.43.5