Move some computations for Form1040 into methods.
[ustaxlib.git] / src / core / Line.ts
1 // Copyright 2020 Blue Static <https://www.bluestatic.org>
2 // This program is free software licensed under the GNU General Public License,
3 // version 3.0. The full text of the license can be found in LICENSE.txt.
4 // SPDX-License-Identifier: GPL-3.0-only
5
6 import TaxReturn from './TaxReturn';
7 import * as Trace from './Trace';
8 import Form, { FormClass } from './Form';
9
10 export abstract class Line<T> {
11 private _description?: string;
12
13 _id: string; // _id is set by Form.init().
14 form: Form; // Set by Form.init();
15
16 constructor(description?: string) {
17 this._description = description;
18 }
19
20 get id(): string {
21 return this._id;
22 }
23
24 get description(): string {
25 return this._description;
26 }
27
28 abstract value(tr: TaxReturn): T;
29 };
30
31 type ComputeFunc<T> = (tr: TaxReturn) => T;
32
33 export class ComputedLine<T> extends Line<T> {
34 private _compute: ComputeFunc<T>;
35
36 constructor(compute: ComputeFunc<T>, description?: string) {
37 super(description);
38 this._compute = compute;
39 }
40
41 value(tr: TaxReturn): T {
42 Trace.begin(this);
43 const value = this._compute(tr);
44 Trace.end();
45 return value;
46 }
47 };
48
49 export class ReferenceLine<F extends Form,
50 L extends keyof F['lines'],
51 T extends ReturnType<F['lines'][L]['value']>>
52 extends Line<T> {
53 private _form: FormClass<F>;
54 private _line: L;
55 private _fallback?: T;
56
57 // If creating a ReferenceLine and F is the same class as the
58 // the one the Line is in, erase |form|'s type with |as any| to
59 // keep TypeScript happy.
60 constructor(form: FormClass<F>, line: L, description?: string, fallback?: T) {
61 super(description || `Reference ${form.name}@${line}`);
62 this._form = form;
63 this._line = line;
64 this._fallback = fallback;
65 }
66
67 value(tr: TaxReturn): T {
68 Trace.begin(this);
69 const form: F = tr.findForm(this._form);
70 if (this._fallback !== undefined && !form) {
71 Trace.end();
72 return this._fallback;
73 }
74 const value: T = form.getValue(tr, this._line);
75 Trace.end();
76 return value;
77 }
78 };
79
80 // SymbolicLine cannot be used for lines defined on F itself. For those cases, use:
81 // new ComputedLine((tr) => this.K(tr));
82 export class SymbolicLine<F extends Form & { [key in K]: ComputeFunc<ReturnType<F[K]>> },
83 K extends keyof F>
84 extends Line<ReturnType<F[K]>> {
85 private _form: FormClass<F>;
86 private _key: K;
87
88 constructor(form: FormClass<F>, key: K, description?: string) {
89 super(description || `Reference ${form.name}/${key}`);
90 this._form = form;
91 this._key = key;
92 }
93
94 value(tr: TaxReturn): ReturnType<F[K]> {
95 Trace.begin(this);
96 const form: F = tr.findForm(this._form);
97 const value = form[this._key](tr);
98 Trace.end();
99 return value;
100 }
101 }
102
103 export class InputLine<U = unknown, T extends keyof U = any> extends Line<U[T]> {
104 private _input: T;
105 private _fallback: U[T];
106
107 form: Form<U>;
108
109 constructor(input: T, description?: string, fallback?: U[T]) {
110 super(description || `Input from ${input}`);
111 this._input = input;
112 this._fallback = fallback;
113 }
114
115 value(tr: TaxReturn): U[T] {
116 Trace.begin(this);
117 if (!this.form.hasInput(this._input) && this._fallback !== undefined) {
118 Trace.end();
119 return this._fallback;
120 }
121 const value = this.form.getInput<T>(this._input);
122 Trace.end();
123 return value;
124 }
125 };
126
127 export class AccumulatorLine<F extends Form,
128 L extends keyof F['lines']>
129 extends Line<number> {
130 private _form: FormClass<F>;
131 private _line: L;
132
133 constructor(form: FormClass<F>, line: L, description?: string) {
134 super(description || `Accumulator ${form.name}@${line}`);
135 this._form = form;
136 this._line = line;
137 }
138
139 value(tr): number {
140 Trace.begin(this);
141 const forms: F[] = tr.findForms(this._form);
142 const value = sumLineOfForms(tr, forms, this._line);
143 Trace.end();
144 return value;
145 }
146 };
147
148 export class UnsupportedLine extends Line<number> {
149 constructor(description?: string) {
150 super(description || 'Unsupported');
151 }
152
153 value(tr): number {
154 // Unsupported lines are deliberately omitted from Trace.
155 return 0;
156 }
157 };
158
159 export function sumLineOfForms<F extends Form, L extends keyof F['lines']>(
160 tr: TaxReturn, forms: F[], line: L): number {
161 const reducer = (acc: number, curr: F) => acc + curr.getValue(tr, line);
162 return forms.reduce(reducer, 0);
163 }
164
165 export function sumFormLines<F extends Form, L extends keyof F['lines']>(
166 tr: TaxReturn, form: F, lines: L[]): number {
167 let value = 0;
168 for (const line of lines)
169 value += form.getValue(tr, line);
170 return value;
171 }