Introduce the LineOptions interface and optional ctor param for Line.
[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 enum FormatType {
11 Dollar = '$',
12 Decimal = '.',
13 Percent = '%',
14 String = 's',
15 }
16
17 interface LineOptions {
18 formatType?: FormatType;
19 }
20
21 export abstract class Line<T> {
22 private _description?: string;
23 private _options?: LineOptions;
24
25 _id: string; // _id is set by Form.init().
26 form: Form; // Set by Form.init();
27
28 constructor(description?: string, options?: LineOptions) {
29 this._description = description;
30 this._options = options;
31 }
32
33 get id(): string {
34 return this._id;
35 }
36
37 get description(): string {
38 return this._description;
39 }
40
41 get options(): LineOptions {
42 return this._options || {};
43 }
44
45 abstract value(tr: TaxReturn): T;
46 };
47
48 type ComputeFunc<T> = (tr: TaxReturn) => T;
49
50 export class ComputedLine<T> extends Line<T> {
51 private _compute: ComputeFunc<T>;
52
53 constructor(compute: ComputeFunc<T>, description?: string, options?: LineOptions) {
54 super(description, options);
55 this._compute = compute;
56 }
57
58 value(tr: TaxReturn): T {
59 Trace.begin(this);
60 const value = this._compute(tr);
61 Trace.end();
62 return value;
63 }
64 };
65
66 export class ReferenceLine<F extends Form,
67 L extends keyof F['lines'],
68 T extends ReturnType<F['lines'][L]['value']>>
69 extends Line<T> {
70 private _form: FormClass<F>;
71 private _line: L;
72 private _fallback?: T;
73
74 // If creating a ReferenceLine and F is the same class as the
75 // the one the Line is in, erase |form|'s type with |as any| to
76 // keep TypeScript happy.
77 constructor(form: FormClass<F>, line: L, description?: string, fallback?: T, options?: LineOptions) {
78 super(description || `Reference ${form.name}@${line}`, options);
79 this._form = form;
80 this._line = line;
81 this._fallback = fallback;
82 }
83
84 value(tr: TaxReturn): T {
85 Trace.begin(this);
86 const form: F = tr.findForm(this._form);
87 if (this._fallback !== undefined && !form) {
88 Trace.end();
89 return this._fallback;
90 }
91 const value: T = form.getValue(tr, this._line);
92 Trace.end();
93 return value;
94 }
95 };
96
97 // SymbolicLine cannot be used for lines defined on F itself. For those cases, use:
98 // new ComputedLine((tr) => this.K(tr));
99 export class SymbolicLine<F extends Form & { [key in K]: ComputeFunc<ReturnType<F[K]>> },
100 K extends keyof F>
101 extends Line<ReturnType<F[K]>> {
102 private _form: FormClass<F>;
103 private _key: K;
104
105 constructor(form: FormClass<F>, key: K, description?: string, options?: LineOptions) {
106 super(description || `Reference ${form.name}/${key}`, options);
107 this._form = form;
108 this._key = key;
109 }
110
111 value(tr: TaxReturn): ReturnType<F[K]> {
112 Trace.begin(this);
113 const form: F = tr.findForm(this._form);
114 const value = form[this._key](tr);
115 Trace.end();
116 return value;
117 }
118 }
119
120 export class InputLine<U = unknown, T extends keyof U = any> extends Line<U[T]> {
121 private _input: T;
122 private _fallback: U[T];
123
124 form: Form<U>;
125
126 constructor(input: T, description?: string, fallback?: U[T], options?: LineOptions) {
127 super(description || `Input from ${input}`, options);
128 this._input = input;
129 this._fallback = fallback;
130 }
131
132 value(tr: TaxReturn): U[T] {
133 Trace.begin(this);
134 if (!this.form.hasInput(this._input) && this._fallback !== undefined) {
135 Trace.end();
136 return this._fallback;
137 }
138 const value = this.form.getInput<T>(this._input);
139 Trace.end();
140 return value;
141 }
142 };
143
144 export class AccumulatorLine<F extends Form,
145 L extends keyof F['lines']>
146 extends Line<number> {
147 private _form: FormClass<F>;
148 private _line: L;
149
150 constructor(form: FormClass<F>, line: L, description?: string, options?: LineOptions) {
151 super(description || `Accumulator ${form.name}@${line}`, options);
152 this._form = form;
153 this._line = line;
154 }
155
156 value(tr): number {
157 Trace.begin(this);
158 const forms: F[] = tr.findForms(this._form);
159 const value = sumLineOfForms(tr, forms, this._line);
160 Trace.end();
161 return value;
162 }
163 };
164
165 export class UnsupportedLine extends Line<number> {
166 constructor(description?: string) {
167 super(description || 'Unsupported');
168 }
169
170 value(tr): number {
171 // Unsupported lines are deliberately omitted from Trace.
172 return 0;
173 }
174 };
175
176 export function sumLineOfForms<F extends Form, L extends keyof F['lines']>(
177 tr: TaxReturn, forms: F[], line: L): number {
178 const reducer = (acc: number, curr: F) => acc + curr.getValue(tr, line);
179 return forms.reduce(reducer, 0);
180 }
181
182 export function sumFormLines<F extends Form, L extends keyof F['lines']>(
183 tr: TaxReturn, form: F, lines: L[]): number {
184 let value = 0;
185 for (const line of lines)
186 value += form.getValue(tr, line);
187 return value;
188 }