test('add and get line', () => {
const l = new ComputedLine<number>(() => 42);
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Test Form';
readonly lines = { '1': l };
});
test('get non-existent line', () => {
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Test';
readonly lines = {};
};
const f = new TestForm();
- const fAsAny: Form<any> = f;
+ const fAsAny: Form = f;
expect(() => fAsAny.getLine('line')).toThrow(NotFoundError);
//TYPEERROR:
filingStatus: string;
money: number;
};
- class TestForm extends Form<any, TestInput> {
+ class TestForm extends Form<TestInput> {
readonly name = '1040';
readonly lines = null;
});
test('get value', () => {
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Form';
readonly lines = {
//TYPEERROR:
//let s: string = f.getValue(tr, 'line');
- const fAsAny: Form<any> = f;
+ const fAsAny: Form = f;
expect(() => fAsAny.getValue(tr, 'other')).toThrow(NotFoundError);
//TYPEERROR:
//expect(() => f.getValue(tr, 'other')).toThrow(NotFoundError);
});
test('form types', () => {
- class FormA extends Form<any> {
+ class FormA extends Form {
readonly name = 'A';
readonly lines = {};
};
- class FormB extends Form<any> {
+ class FormB extends Form {
readonly name = 'B';
readonly lines = {};
};
import { Line } from './Line';
import { InconsistencyError, NotFoundError } from './Errors';
-export default abstract class Form<L extends { [key: string]: Line<any> },
- I = unknown> {
+export default abstract class Form<I = unknown> {
abstract readonly name: string;
- abstract readonly lines: L;
+ abstract readonly lines: { [key: string]: Line<any> };
readonly supportsMultipleCopies: boolean = false;
return undefined;
}
- getLine<K extends keyof L>(id: K): L[K] {
+ getLine<K extends keyof this['lines']>(id: K): this['lines'][K] {
if (!(id in this.lines))
throw new NotFoundError(`Form ${this.name} does not have line ${id}`);
- return this.lines[id];
+ // This coercion is safe: the method's generic constraint for K ensures
+ // a valid key in |lines|, and the abstract declaration of |lines| ensures
+ // the correct index type.
+ return this.lines[id as any] as this['lines'][K];
}
- getValue<K extends keyof L>(tr: TaxReturn, id: K): ReturnType<L[K]['value']> {
- const line: L[K] = this.getLine(id);
+ getValue<K extends keyof this['lines']>(tr: TaxReturn, id: K): ReturnType<this['lines'][K]['value']> {
+ const line = this.getLine(id);
return line.value(tr);
}
}
};
-export type FormClass<T extends Form<any>> = new (...args: any[]) => T;
+export type FormClass<T extends Form> = new (...args: any[]) => T;
-export function isFormT<T extends Form<any>>(form: Form<any>,
- formClass: FormClass<T>):
- form is T {
+export function isFormT<T extends Form>(form: Form,
+ formClass: FormClass<T>):
+ form is T {
return form.constructor === formClass;
}
});
test('reference line', () => {
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Form 1';
readonly lines = {
'6b': new ConstantLine(12.34),
});
test('self reference line', () => {
- class OtherForm extends Form<OtherForm['lines']> {
+ class OtherForm extends Form {
readonly name = 'Form A';
readonly lines = {
'6c': new ConstantLine(55)
};
};
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Form 1';
readonly lines = {
'a': new ConstantLine(100.2),
'b': new ReferenceLine(OtherForm, '6c'),
- 'c': new ReferenceLine((TestForm as unknown) as FormClass<Form<any>>, 'b'),
+ 'c': new ReferenceLine((TestForm as unknown) as FormClass<Form>, 'b'),
'd': new ReferenceLine(TestForm as any, 'b'),
};
};
key: string;
key2?: string;
}
- class TestForm extends Form<TestForm['lines'], Input> {
+ class TestForm extends Form<Input> {
readonly name = 'F1';
readonly lines = {
'1': new InputLine<Input>('key'),
});
test('line stack', () => {
- class FormZ extends Form<FormZ['lines'], {'input': number}> {
+ class FormZ extends Form<{'input': number}> {
readonly name = 'Z';
readonly lines = {
'3': new InputLine<any, any>('input')
}
};
- class FormZ2 extends Form<FormZ2['lines']> {
+ class FormZ2 extends Form {
readonly name = 'Z-2';
readonly lines = {
'2c': new ComputedLine<number>((tr: TaxReturn): any => {
});
test('accumulator line', () => {
- class TestForm extends Form<TestForm['lines']> {
+ class TestForm extends Form {
readonly name = 'Form B';
readonly supportsMultipleCopies = true;
readonly lines = {
private _description?: string;
_id: string; // _id is set by Form.init().
- form: Form<any, any>; // Set by Form.init();
+ form: Form; // Set by Form.init();
constructor(description?: string) {
this._description = description;
}
};
-export class ReferenceLine<F extends Form<any>,
+export class ReferenceLine<F extends Form,
L extends keyof F['lines'],
T extends ReturnType<F['lines'][L]['value']>>
extends Line<T> {
private _input: T;
private _fallback: U[T];
- form: Form<any, U>;
+ form: Form<U>;
constructor(input: T, description?: string, fallback?: U[T]) {
super(description || `Input from ${input}`);
}
};
-export class AccumulatorLine<F extends Form<any>,
+export class AccumulatorLine<F extends Form,
L extends keyof F['lines']>
extends Line<number> {
private _form: FormClass<F>;
}
};
-export function sumLineOfForms<F extends Form<any>, L extends keyof F['lines']>(
+export function sumLineOfForms<F extends Form, L extends keyof F['lines']>(
tr: TaxReturn, forms: F[], line: L): number {
const reducer = (acc: number, curr: F) => acc + curr.getValue(tr, line);
return forms.reduce(reducer, 0);
}
-export function sumFormLines<F extends Form<any>, L extends keyof F['lines']>(
+export function sumFormLines<F extends Form, L extends keyof F['lines']>(
tr: TaxReturn, form: F, lines: L[]): number {
let value = 0;
for (const line of lines)
});
test('single-copy forms', () => {
- class TestForm extends Form<null> {
+ class TestForm extends Form {
readonly name = 'Test Form';
readonly lines = null;
};
});
test('multiple-copy forms', () => {
- class TestForm extends Form<null> {
+ class TestForm extends Form {
readonly name = 'Test Form';
readonly supportsMultipleCopies = true;
readonly lines = null;
});
test('get non-existent form', () => {
- class TestForm extends Form<null> {
+ class TestForm extends Form {
readonly name = 'Test Form';
readonly lines = null;
}
expect(tr.findForms(TestForm)).toEqual([]);
});
-class PerPersonForm extends Form<PerPersonForm['lines']> {
+class PerPersonForm extends Form {
private _person?: Person;
readonly name = 'Per Person';
export default abstract class TaxReturn {
private _people: Person[] = [];
- private _forms: Form<any, unknown>[] = [];
+ private _forms: Form[] = [];
abstract get year(): number;
abstract get includeJointPersonForms(): boolean;
- get forms(): Form<any, unknown>[] {
+ get forms(): Form[] {
return [...this._forms];
}
return people[0];
}
- addForm(form: Form<any>) {
+ addForm(form: Form) {
if (!form.supportsMultipleCopies) {
- const other = this.findForms(form.constructor as FormClass<Form<any>>);
+ const other = this.findForms(form.constructor as FormClass<Form>);
if (other.length > 0) {
throw new InconsistencyError(`Cannot have more than one type of form ${form.name}`);
}
this._forms.push(form);
}
- findForm<T extends Form<any>>(cls: FormClass<T>): T | null {
+ findForm<T extends Form>(cls: FormClass<T>): T | null {
const forms = this.findForms(cls);
if (forms.length == 0)
return null;
return forms[0];
}
- findForms<T extends Form<any>>(cls: FormClass<T>): T[] {
+ findForms<T extends Form>(cls: FormClass<T>): T[] {
const forms: T[] = this._forms
- .filter((form: Form<any>): form is T => isFormT(form, cls))
+ .filter((form: Form): form is T => isFormT(form, cls))
.filter((form: T) => {
const person = form.person();
if (person === undefined)
return forms;
}
- getForm<T extends Form<any>>(cls: FormClass<T>): T {
+ getForm<T extends Form>(cls: FormClass<T>): T {
const form = this.findForm(cls);
if (!form)
throw new NotFoundError(`No form ${cls.name}`);
value: number;
};
-class TestForm extends Form<TestForm['lines'], Input> {
+class TestForm extends Form<Input> {
readonly name = 'TF';
readonly lines = {
filingStatus: FilingStatus;
};
-export default class Form1040 extends Form<Form1040['lines'], Form1040Input> {
+export default class Form1040 extends Form<Form1040Input> {
readonly name = '1040';
readonly lines = {
'13a': new UnsupportedLine('Child tax credit'),
'13b': new ComputedLine((tr): number => {
- let value = this.getValue(tr, '13a');
+ let value: number = this.getValue(tr, '13a');
const sched3 = tr.findForm(Schedule3);
if (sched3)
value += undefinedToZero(sched3.getValue(tr, '7'));
return ((income - bracketStart) * bracket[1]) + bracket[2];
};
-export class QDCGTaxWorksheet extends Form<QDCGTaxWorksheet['lines']> {
+export class QDCGTaxWorksheet extends Form {
readonly name = 'QDCG Tax Worksheet';
readonly lines = {
class Input<T extends keyof Form1099BInput> extends InputLine<Form1099BInput, T> {};
-export default class Form1099B extends Form<Form1099B['lines'], Form1099BInput> {
+export default class Form1099B extends Form<Form1099BInput> {
readonly name = '1099-B';
readonly supportsMultipleCopies = true;
class Input<T extends keyof Form1099DIVInput> extends InputLine<Form1099DIVInput, T> {};
-export default class Form1099DIV extends Form<Form1099DIV['lines'], Form1099DIVInput> {
+export default class Form1099DIV extends Form<Form1099DIVInput> {
readonly name = '1099-DIV';
readonly supportsMultipleCopies = true;
class Input<T extends keyof Form1099INTInput> extends InputLine<Form1099INTInput, T> {};
-export default class Form1099INT extends Form<Form1099INT['lines'], Form1099INTInput> {
+export default class Form1099INT extends Form<Form1099INTInput> {
readonly name = '1099-INT';
readonly supportsMultipleCopies = true;
class Input<T extends keyof Form1099RInput> extends InputLine<Form1099RInput, T> {};
-export default class Form1099R extends Form<Form1099R['lines'], Form1099RInput> {
+export default class Form1099R extends Form<Form1099RInput> {
readonly name = '1099-R';
readonly supportsMultipleCopies = true;
class Input<T extends keyof Form1116Input> extends InputLine<Form1116Input, T> {};
-export default class Form1116 extends Form<Form1116['lines'], Form1116Input> {
+export default class Form1116 extends Form<Form1116Input> {
readonly name = '1116';
readonly lines = {
import Schedule3 from './Schedule3';
import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
-export default class Form6251 extends Form<Form6251['lines']> {
+export default class Form6251 extends Form {
readonly name = '6251';
readonly lines = {
class Input<T extends keyof Form8606Input> extends InputLine<Form8606Input, T> {};
-export default class Form8606 extends Form<Form8606['lines'], Form8606Input> {
+export default class Form8606 extends Form<Form8606Input> {
readonly name = '8606';
readonly supportsMultipleCopies = true;
}
};
-export default class Form8949 extends Form<Form8949['lines']> {
+export default class Form8949 extends Form {
readonly name = '8949';
readonly supportsMultipleCopies = true;
import Form1040, { FilingStatus } from './Form1040';
import W2 from './W2';
-export default class Form8959 extends Form<Form8959['lines']> {
+export default class Form8959 extends Form {
readonly name = '8959';
readonly lines = {
import Form1040, { FilingStatus } from './Form1040';
import Schedule1 from './Schedule1';
-export default class Form8960 extends Form<Form8960['lines']> {
+export default class Form8960 extends Form {
readonly name = '8960';
readonly lines = {
// Dividends from REITs get preferential tax treatment, but the form used to
// calculate that differs based off taxable income amounts. But the QBI
// computation for RETIs is the same. This just models the REIT portion.
-export default class Form8995REIT extends Form<Form8995REIT['lines']> {
+export default class Form8995REIT extends Form {
readonly name = '8995 REIT';
// This uses line numbers from 8995-A.
}
};
-export default class Schedule1 extends Form<Schedule1['lines'], Schedule1Input> {
+export default class Schedule1 extends Form<Schedule1Input> {
readonly name = 'Schedule 1';
readonly lines = {
prevYearFilingStatus?: FilingStatus;
};
-export class SALTWorksheet extends Form<SALTWorksheet['lines'], SALTWorksheetInput> {
+export class SALTWorksheet extends Form<SALTWorksheetInput> {
readonly name = 'SALT Refund Worksheet';
readonly lines = {
import Form8959 from './Form8959';
import Form8960 from './Form8960';
-export default class Schedule2 extends Form<Schedule2['lines']> {
+export default class Schedule2 extends Form {
readonly name = 'Schedule 2';
readonly lines = {
estimatedTaxPayments?: number;
};
-export default class Schedule3 extends Form<Schedule3['lines'], Schedule3Input> {
+export default class Schedule3 extends Form<Schedule3Input> {
readonly name = 'Schedule 3';
readonly lines = {
class Input extends InputLine<ScheduleAInput> {};
-export default class ScheduleA extends Form<ScheduleA['lines'], ScheduleAInput> {
+export default class ScheduleA extends Form<ScheduleAInput> {
readonly name = 'Schedule A';
readonly lines = {
import Form1099DIV from './Form1099DIV';
import Form1040, { FilingStatus, QDCGTaxWorksheet, computeTax } from './Form1040';
-export default class ScheduleD extends Form<ScheduleD['lines']> {
+export default class ScheduleD extends Form {
readonly name = 'Schedule D';
readonly lines = {
};
};
-export class ScheduleDTaxWorksheet extends Form<ScheduleDTaxWorksheet['lines']> {
+export class ScheduleDTaxWorksheet extends Form {
readonly name = 'Schedule D Tax Worksheet';
readonly lines = {
class Input<T extends keyof W2Input> extends InputLine<W2Input, T> {};
-export default class W2 extends Form<W2['lines'], W2Input> {
+export default class W2 extends Form<W2Input> {
readonly name = 'W-2';
readonly supportsMultipleCopies = true;