Aspine / aspine

Alternate frontend to Aspen, making it easier for CPSD students to access and predict their grades
https://aspine.cpsd.us
GNU General Public License v3.0
14 stars 22 forks source link

Type safety #153

Open psvenk opened 3 years ago

psvenk commented 3 years ago

Gradually move the codebase to have (optional) type checking, in the form of JSDoc comments in the source code that can be checked by tsc (the TypeScript compiler). Such an arrangement confers the advantages of type safety without introducing a build step because the source code will continue to be executable JavaScript. Documentation about this feature: https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html

The first step is adjusting the codebase (backend and frontend) to type check without adding many type annotations. Currently, running tsc gives failures because there are places where we abuse JavaScript's flexibility by assigning a number or a string to a variable depending on context, for example.

Next, we would need to write type definitions for the data structures that we use (these can be in separate .ts files that are included in .js files via @include comments), such as tableData and currentTableData, and add type annotations so that TypeScript can type-check more of the code (otherwise, the default is for TypeScript to assume that every variable has type any, which nearly disables type checking). Eventually, we may want to enable the noImplicitAny option which causes TypeScript to require type annotations wherever type inference cannot be performed. Some work on this step has been done in pull request #128.

psvenk commented 3 years ago

Here is a mockup of type definitions that I wrote on June 21. This has not been tested on the actual code; I had written it only to give an initial idea of what type definitions would look like.

Show code ```typescript export type TableDataObject = { cumGPA: GPA, currentTermData: Term, name: string, overview: OverviewItem[], recent: Recent, schedule: Schedule, terms: Terms, username: string, }; export type GPA = { outOfFive: number, outOfFour: number, percent: number, }; export type Terms = { // "Current Quarter" in Aspen current: Term, q1: Term, q2: Term, q3: Term, q4: Term, }; export type Term = { GPA: GPA, calcGPA: GPA, classes: Class[], }; export type Class = { assignments: Assignment[], // Not sure why this exists as a string calculated_grade: string, // Like a Map; should be a Map categories: object, categoryDisplay: CategoryDisplay[], // Not sure what this does categoryGrades: {}, color: string, decimals: number, edited: boolean, // Grade as displayed grade: string, // This is the actual calculated grade as a decimal init_calculated_grade: number, name: string, tokens: Tokens, // See public/js/calculate_grade.js type: "categoryPercent", }; export type Tokens = { apache_token: string, session_id: string, }; export type OverviewItem = { // These are all strings because they come from Aspen and for some reason // we do not do parseInt or parseFloat anywhere absent: string, class: string, dismissed: string, q1: string, q2: string, q3: string, q4: string, tardy: string, teacher: string, term: string, ytd: string, }; export type Recent = { // These two are empty arrays at the moment because Aspen does not show // recent activity and attendance for some reason since January or so. // The actual type of the items would probably be some sort of object. recentActivityArray: any[], recentAttendanceArray: any[], studentName: string, }; export type Schedule = { black: ScheduleItem[], silver: ScheduleItem[], }; export type ScheduleItem = { aspenPeriod: string, class: string, color: string, id: string, name: string, period: string, room: string, teacher: string, }; let tableData: TableDataObject[]; // Index currently selected in tableData dropdown let currentTableDataIndex: number; let currentTableData: TableDataObject = tableData[currentTableDataIndex]; assert( // currentTerm: term currently selected in term dropdown currentTableData.currentTermData === currentTableData.terms[currentTerm] ); ```
psvenk commented 3 years ago

Another advantage of type safety is that we can share some type definitions between the frontend and the backend so that they act as a contract between the two, making sure that the API is consistent (at least in the type of data to expect in each place). This should also alleviate confusion surrounding the naming of tableData, currentTableData, currentTerm, etc.

psvenk commented 3 years ago

Because we are rewriting scrape.js anyway for #184, @notrodes suggested that we simply rewrite it in TypeScript instead of using JSDoc.