From 345ee4ed5625dfb934cca6fa73439601374d4d27 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 18 Oct 2020 02:12:59 -0400 Subject: [PATCH] Remove the need to self-reference Form['lines'] in Form subclasses. This self-reference is not supported in TypeSciprt 4 (see https://github.com/microsoft/TypeScript/issues/40315), and it is unnecessary. --- src/core/Form.test.ts | 16 ++++++++-------- src/core/Form.ts | 24 +++++++++++++----------- src/core/Line.test.ts | 16 ++++++++-------- src/core/Line.ts | 12 ++++++------ src/core/TaxReturn.test.ts | 8 ++++---- src/core/TaxReturn.ts | 16 ++++++++-------- src/core/Trace.test.ts | 2 +- src/fed2019/Form1040.ts | 6 +++--- src/fed2019/Form1099B.ts | 2 +- src/fed2019/Form1099DIV.ts | 2 +- src/fed2019/Form1099INT.ts | 2 +- src/fed2019/Form1099R.ts | 2 +- src/fed2019/Form1116.ts | 2 +- src/fed2019/Form6251.ts | 2 +- src/fed2019/Form8606.ts | 2 +- src/fed2019/Form8949.ts | 2 +- src/fed2019/Form8959.ts | 2 +- src/fed2019/Form8960.ts | 2 +- src/fed2019/Form8995.ts | 2 +- src/fed2019/Schedule1.ts | 4 ++-- src/fed2019/Schedule2.ts | 2 +- src/fed2019/Schedule3.ts | 2 +- src/fed2019/ScheduleA.ts | 2 +- src/fed2019/ScheduleD.ts | 4 ++-- src/fed2019/W2.ts | 2 +- 25 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/core/Form.test.ts b/src/core/Form.test.ts index 8098759..4178d19 100644 --- a/src/core/Form.test.ts +++ b/src/core/Form.test.ts @@ -16,7 +16,7 @@ class TestTaxReturn extends TaxReturn { test('add and get line', () => { const l = new ComputedLine(() => 42); - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Test Form'; readonly lines = { '1': l }; @@ -27,13 +27,13 @@ test('add and get line', () => { }); test('get non-existent line', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Test'; readonly lines = {}; }; const f = new TestForm(); - const fAsAny: Form = f; + const fAsAny: Form = f; expect(() => fAsAny.getLine('line')).toThrow(NotFoundError); //TYPEERROR: @@ -45,7 +45,7 @@ test('input', () => { filingStatus: string; money: number; }; - class TestForm extends Form { + class TestForm extends Form { readonly name = '1040'; readonly lines = null; @@ -56,7 +56,7 @@ test('input', () => { }); test('get value', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Form'; readonly lines = { @@ -71,18 +71,18 @@ test('get value', () => { //TYPEERROR: //let s: string = f.getValue(tr, 'line'); - const fAsAny: Form = 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 { + class FormA extends Form { readonly name = 'A'; readonly lines = {}; }; - class FormB extends Form { + class FormB extends Form { readonly name = 'B'; readonly lines = {}; }; diff --git a/src/core/Form.ts b/src/core/Form.ts index c713f17..dc7e70a 100644 --- a/src/core/Form.ts +++ b/src/core/Form.ts @@ -9,11 +9,10 @@ import * as Trace from './Trace'; import { Line } from './Line'; import { InconsistencyError, NotFoundError } from './Errors'; -export default abstract class Form }, - I = unknown> { +export default abstract class Form { abstract readonly name: string; - abstract readonly lines: L; + abstract readonly lines: { [key: string]: Line }; readonly supportsMultipleCopies: boolean = false; @@ -35,14 +34,17 @@ export default abstract class Form }, return undefined; } - getLine(id: K): L[K] { + getLine(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(tr: TaxReturn, id: K): ReturnType { - const line: L[K] = this.getLine(id); + getValue(tr: TaxReturn, id: K): ReturnType { + const line = this.getLine(id); return line.value(tr); } @@ -59,10 +61,10 @@ export default abstract class Form }, } }; -export type FormClass> = new (...args: any[]) => T; +export type FormClass = new (...args: any[]) => T; -export function isFormT>(form: Form, - formClass: FormClass): - form is T { +export function isFormT(form: Form, + formClass: FormClass): + form is T { return form.constructor === formClass; } diff --git a/src/core/Line.test.ts b/src/core/Line.test.ts index 27ec6a3..4a59638 100644 --- a/src/core/Line.test.ts +++ b/src/core/Line.test.ts @@ -39,7 +39,7 @@ test('computed line', () => { }); test('reference line', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Form 1'; readonly lines = { '6b': new ConstantLine(12.34), @@ -65,18 +65,18 @@ test('reference line', () => { }); test('self reference line', () => { - class OtherForm extends Form { + class OtherForm extends Form { readonly name = 'Form A'; readonly lines = { '6c': new ConstantLine(55) }; }; - class TestForm extends Form { + 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>, 'b'), + 'c': new ReferenceLine((TestForm as unknown) as FormClass
, 'b'), 'd': new ReferenceLine(TestForm as any, 'b'), }; }; @@ -97,7 +97,7 @@ test('input line', () => { key: string; key2?: string; } - class TestForm extends Form { + class TestForm extends Form { readonly name = 'F1'; readonly lines = { '1': new InputLine('key'), @@ -119,14 +119,14 @@ test('input line', () => { }); test('line stack', () => { - class FormZ extends Form { + class FormZ extends Form<{'input': number}> { readonly name = 'Z'; readonly lines = { '3': new InputLine('input') } }; - class FormZ2 extends Form { + class FormZ2 extends Form { readonly name = 'Z-2'; readonly lines = { '2c': new ComputedLine((tr: TaxReturn): any => { @@ -144,7 +144,7 @@ test('line stack', () => { }); test('accumulator line', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Form B'; readonly supportsMultipleCopies = true; readonly lines = { diff --git a/src/core/Line.ts b/src/core/Line.ts index 8995bee..821483f 100644 --- a/src/core/Line.ts +++ b/src/core/Line.ts @@ -11,7 +11,7 @@ export abstract class Line { private _description?: string; _id: string; // _id is set by Form.init(). - form: Form; // Set by Form.init(); + form: Form; // Set by Form.init(); constructor(description?: string) { this._description = description; @@ -46,7 +46,7 @@ export class ComputedLine extends Line { } }; -export class ReferenceLine, +export class ReferenceLine> extends Line { @@ -81,7 +81,7 @@ export class InputLine extends Line private _input: T; private _fallback: U[T]; - form: Form; + form: Form; constructor(input: T, description?: string, fallback?: U[T]) { super(description || `Input from ${input}`); @@ -101,7 +101,7 @@ export class InputLine extends Line } }; -export class AccumulatorLine, +export class AccumulatorLine extends Line { private _form: FormClass; @@ -133,13 +133,13 @@ export class UnsupportedLine extends Line { } }; -export function sumLineOfForms, L extends keyof F['lines']>( +export function sumLineOfForms( 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, L extends keyof F['lines']>( +export function sumFormLines( tr: TaxReturn, form: F, lines: L[]): number { let value = 0; for (const line of lines) diff --git a/src/core/TaxReturn.test.ts b/src/core/TaxReturn.test.ts index 4da4fcb..664e529 100644 --- a/src/core/TaxReturn.test.ts +++ b/src/core/TaxReturn.test.ts @@ -59,7 +59,7 @@ test('get non-existent person', () => { }); test('single-copy forms', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Test Form'; readonly lines = null; }; @@ -73,7 +73,7 @@ test('single-copy forms', () => { }); test('multiple-copy forms', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Test Form'; readonly supportsMultipleCopies = true; readonly lines = null; @@ -97,7 +97,7 @@ test('multiple-copy forms', () => { }); test('get non-existent form', () => { - class TestForm extends Form { + class TestForm extends Form { readonly name = 'Test Form'; readonly lines = null; } @@ -107,7 +107,7 @@ test('get non-existent form', () => { expect(tr.findForms(TestForm)).toEqual([]); }); -class PerPersonForm extends Form { +class PerPersonForm extends Form { private _person?: Person; readonly name = 'Per Person'; diff --git a/src/core/TaxReturn.ts b/src/core/TaxReturn.ts index cce8132..ccd9ee8 100644 --- a/src/core/TaxReturn.ts +++ b/src/core/TaxReturn.ts @@ -9,13 +9,13 @@ import { NotFoundError, InconsistencyError, UnsupportedFeatureError } from './Er export default abstract class TaxReturn { private _people: Person[] = []; - private _forms: Form[] = []; + private _forms: Form[] = []; abstract get year(): number; abstract get includeJointPersonForms(): boolean; - get forms(): Form[] { + get forms(): Form[] { return [...this._forms]; } @@ -40,9 +40,9 @@ export default abstract class TaxReturn { return people[0]; } - addForm(form: Form) { + addForm(form: Form) { if (!form.supportsMultipleCopies) { - const other = this.findForms(form.constructor as FormClass>); + const other = this.findForms(form.constructor as FormClass); if (other.length > 0) { throw new InconsistencyError(`Cannot have more than one type of form ${form.name}`); } @@ -51,7 +51,7 @@ export default abstract class TaxReturn { this._forms.push(form); } - findForm>(cls: FormClass): T | null { + findForm(cls: FormClass): T | null { const forms = this.findForms(cls); if (forms.length == 0) return null; @@ -60,9 +60,9 @@ export default abstract class TaxReturn { return forms[0]; } - findForms>(cls: FormClass): T[] { + findForms(cls: FormClass): T[] { const forms: T[] = this._forms - .filter((form: Form): 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) @@ -76,7 +76,7 @@ export default abstract class TaxReturn { return forms; } - getForm>(cls: FormClass): T { + getForm(cls: FormClass): T { const form = this.findForm(cls); if (!form) throw new NotFoundError(`No form ${cls.name}`); diff --git a/src/core/Trace.test.ts b/src/core/Trace.test.ts index 1bdf55b..75130cb 100644 --- a/src/core/Trace.test.ts +++ b/src/core/Trace.test.ts @@ -19,7 +19,7 @@ interface Input { value: number; }; -class TestForm extends Form { +class TestForm extends Form { readonly name = 'TF'; readonly lines = { diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index 579e8eb..edcfb11 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -31,7 +31,7 @@ export interface Form1040Input { filingStatus: FilingStatus; }; -export default class Form1040 extends Form { +export default class Form1040 extends Form { readonly name = '1040'; readonly lines = { @@ -146,7 +146,7 @@ export default class Form1040 extends Form { '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')); @@ -253,7 +253,7 @@ export function computeTax(income: number, filingStatus: FilingStatus): number { return ((income - bracketStart) * bracket[1]) + bracket[2]; }; -export class QDCGTaxWorksheet extends Form { +export class QDCGTaxWorksheet extends Form { readonly name = 'QDCG Tax Worksheet'; readonly lines = { diff --git a/src/fed2019/Form1099B.ts b/src/fed2019/Form1099B.ts index 67a41a3..e4f3680 100644 --- a/src/fed2019/Form1099B.ts +++ b/src/fed2019/Form1099B.ts @@ -36,7 +36,7 @@ export interface Form1099BInput { class Input extends InputLine {}; -export default class Form1099B extends Form { +export default class Form1099B extends Form { readonly name = '1099-B'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form1099DIV.ts b/src/fed2019/Form1099DIV.ts index dc00476..d249011 100644 --- a/src/fed2019/Form1099DIV.ts +++ b/src/fed2019/Form1099DIV.ts @@ -29,7 +29,7 @@ export interface Form1099DIVInput { class Input extends InputLine {}; -export default class Form1099DIV extends Form { +export default class Form1099DIV extends Form { readonly name = '1099-DIV'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form1099INT.ts b/src/fed2019/Form1099INT.ts index b038f99..33a8a6a 100644 --- a/src/fed2019/Form1099INT.ts +++ b/src/fed2019/Form1099INT.ts @@ -26,7 +26,7 @@ export interface Form1099INTInput { class Input extends InputLine {}; -export default class Form1099INT extends Form { +export default class Form1099INT extends Form { readonly name = '1099-INT'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form1099R.ts b/src/fed2019/Form1099R.ts index 744741a..c79be51 100644 --- a/src/fed2019/Form1099R.ts +++ b/src/fed2019/Form1099R.ts @@ -54,7 +54,7 @@ export interface Form1099RInput { class Input extends InputLine {}; -export default class Form1099R extends Form { +export default class Form1099R extends Form { readonly name = '1099-R'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form1116.ts b/src/fed2019/Form1116.ts index 61cc81b..d5dcdfd 100644 --- a/src/fed2019/Form1116.ts +++ b/src/fed2019/Form1116.ts @@ -34,7 +34,7 @@ export interface Form1116Input { class Input extends InputLine {}; -export default class Form1116 extends Form { +export default class Form1116 extends Form { readonly name = '1116'; readonly lines = { diff --git a/src/fed2019/Form6251.ts b/src/fed2019/Form6251.ts index af663cc..2dab257 100644 --- a/src/fed2019/Form6251.ts +++ b/src/fed2019/Form6251.ts @@ -14,7 +14,7 @@ import Schedule2 from './Schedule2'; import Schedule3 from './Schedule3'; import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD'; -export default class Form6251 extends Form { +export default class Form6251 extends Form { readonly name = '6251'; readonly lines = { diff --git a/src/fed2019/Form8606.ts b/src/fed2019/Form8606.ts index 7d8d4b5..4938de3 100644 --- a/src/fed2019/Form8606.ts +++ b/src/fed2019/Form8606.ts @@ -20,7 +20,7 @@ export interface Form8606Input { class Input extends InputLine {}; -export default class Form8606 extends Form { +export default class Form8606 extends Form { readonly name = '8606'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form8949.ts b/src/fed2019/Form8949.ts index 51bd291..67e0ab6 100644 --- a/src/fed2019/Form8949.ts +++ b/src/fed2019/Form8949.ts @@ -76,7 +76,7 @@ class Form8949Line extends Line { } }; -export default class Form8949 extends Form { +export default class Form8949 extends Form { readonly name = '8949'; readonly supportsMultipleCopies = true; diff --git a/src/fed2019/Form8959.ts b/src/fed2019/Form8959.ts index a4ebc9f..0aef950 100644 --- a/src/fed2019/Form8959.ts +++ b/src/fed2019/Form8959.ts @@ -10,7 +10,7 @@ import { clampToZero } from '../core/Math'; import Form1040, { FilingStatus } from './Form1040'; import W2 from './W2'; -export default class Form8959 extends Form { +export default class Form8959 extends Form { readonly name = '8959'; readonly lines = { diff --git a/src/fed2019/Form8960.ts b/src/fed2019/Form8960.ts index 724303e..3591389 100644 --- a/src/fed2019/Form8960.ts +++ b/src/fed2019/Form8960.ts @@ -10,7 +10,7 @@ import { clampToZero, undefinedToZero } from '../core/Math'; import Form1040, { FilingStatus } from './Form1040'; import Schedule1 from './Schedule1'; -export default class Form8960 extends Form { +export default class Form8960 extends Form { readonly name = '8960'; readonly lines = { diff --git a/src/fed2019/Form8995.ts b/src/fed2019/Form8995.ts index 52bcbb9..1590fb7 100644 --- a/src/fed2019/Form8995.ts +++ b/src/fed2019/Form8995.ts @@ -18,7 +18,7 @@ export interface Form8995REITInput { // 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 { +export default class Form8995REIT extends Form { readonly name = '8995 REIT'; // This uses line numbers from 8995-A. diff --git a/src/fed2019/Schedule1.ts b/src/fed2019/Schedule1.ts index ef6707c..a7a4fe4 100644 --- a/src/fed2019/Schedule1.ts +++ b/src/fed2019/Schedule1.ts @@ -58,7 +58,7 @@ class Input extends InputLine } }; -export default class Schedule1 extends Form { +export default class Schedule1 extends Form { readonly name = 'Schedule 1'; readonly lines = { @@ -150,7 +150,7 @@ export interface SALTWorksheetInput { prevYearFilingStatus?: FilingStatus; }; -export class SALTWorksheet extends Form { +export class SALTWorksheet extends Form { readonly name = 'SALT Refund Worksheet'; readonly lines = { diff --git a/src/fed2019/Schedule2.ts b/src/fed2019/Schedule2.ts index 87e2819..20e42a4 100644 --- a/src/fed2019/Schedule2.ts +++ b/src/fed2019/Schedule2.ts @@ -14,7 +14,7 @@ import Form6251 from './Form6251'; import Form8959 from './Form8959'; import Form8960 from './Form8960'; -export default class Schedule2 extends Form { +export default class Schedule2 extends Form { readonly name = 'Schedule 2'; readonly lines = { diff --git a/src/fed2019/Schedule3.ts b/src/fed2019/Schedule3.ts index 0557288..776f1ba 100644 --- a/src/fed2019/Schedule3.ts +++ b/src/fed2019/Schedule3.ts @@ -17,7 +17,7 @@ export interface Schedule3Input { estimatedTaxPayments?: number; }; -export default class Schedule3 extends Form { +export default class Schedule3 extends Form { readonly name = 'Schedule 3'; readonly lines = { diff --git a/src/fed2019/ScheduleA.ts b/src/fed2019/ScheduleA.ts index 8331a62..acb49cd 100644 --- a/src/fed2019/ScheduleA.ts +++ b/src/fed2019/ScheduleA.ts @@ -34,7 +34,7 @@ export interface ScheduleAInput { class Input extends InputLine {}; -export default class ScheduleA extends Form { +export default class ScheduleA extends Form { readonly name = 'Schedule A'; readonly lines = { diff --git a/src/fed2019/ScheduleD.ts b/src/fed2019/ScheduleD.ts index 0925dc9..e46f21b 100644 --- a/src/fed2019/ScheduleD.ts +++ b/src/fed2019/ScheduleD.ts @@ -12,7 +12,7 @@ import Form8949, { Form8949Box } from './Form8949'; import Form1099DIV from './Form1099DIV'; import Form1040, { FilingStatus, QDCGTaxWorksheet, computeTax } from './Form1040'; -export default class ScheduleD extends Form { +export default class ScheduleD extends Form { readonly name = 'Schedule D'; readonly lines = { @@ -85,7 +85,7 @@ export default class ScheduleD extends Form { }; }; -export class ScheduleDTaxWorksheet extends Form { +export class ScheduleDTaxWorksheet extends Form { readonly name = 'Schedule D Tax Worksheet'; readonly lines = { diff --git a/src/fed2019/W2.ts b/src/fed2019/W2.ts index a0f7fff..f4ba24e 100644 --- a/src/fed2019/W2.ts +++ b/src/fed2019/W2.ts @@ -37,7 +37,7 @@ export interface W2Input { class Input extends InputLine {}; -export default class W2 extends Form { +export default class W2 extends Form { readonly name = 'W-2'; readonly supportsMultipleCopies = true; -- 2.22.5