Remove the need to self-reference Form['lines'] in Form subclasses.
[ustaxlib.git] / src / core / Form.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 Person from './Person';
7 import TaxReturn from './TaxReturn';
8 import * as Trace from './Trace';
9 import { Line } from './Line';
10 import { InconsistencyError, NotFoundError } from './Errors';
11
12 export default abstract class Form<I = unknown> {
13 abstract readonly name: string;
14
15 abstract readonly lines: { [key: string]: Line<any> };
16
17 readonly supportsMultipleCopies: boolean = false;
18
19 private readonly _input?: I;
20
21 constructor(input?: I) {
22 this._input = input;
23 }
24
25 init() {
26 for (const id in this.lines) {
27 let l = this.lines[id];
28 l._id = id;
29 l.form = this;
30 }
31 }
32
33 person(): Person | undefined {
34 return undefined;
35 }
36
37 getLine<K extends keyof this['lines']>(id: K): this['lines'][K] {
38 if (!(id in this.lines))
39 throw new NotFoundError(`Form ${this.name} does not have line ${id}`);
40 // This coercion is safe: the method's generic constraint for K ensures
41 // a valid key in |lines|, and the abstract declaration of |lines| ensures
42 // the correct index type.
43 return this.lines[id as any] as this['lines'][K];
44 }
45
46 getValue<K extends keyof this['lines']>(tr: TaxReturn, id: K): ReturnType<this['lines'][K]['value']> {
47 const line = this.getLine(id);
48 return line.value(tr);
49 }
50
51 getInput<K extends keyof I>(name: K): I[K] {
52 if (!(name in this._input)) {
53 throw new NotFoundError(`No input with key ${name} on form ${this.name}`);
54 }
55 Trace.mark(`${this.name} input: ${name}`);
56 return this._input[name];
57 }
58
59 hasInput<K extends keyof I>(name: K): boolean {
60 return this._input !== undefined && name in this._input;
61 }
62 };
63
64 export type FormClass<T extends Form> = new (...args: any[]) => T;
65
66 export function isFormT<T extends Form>(form: Form,
67 formClass: FormClass<T>):
68 form is T {
69 return form.constructor === formClass;
70 }