Skip to content

Migrate to TypeScript v5 #2850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4254295
Bump TypeScript to v5.4
RichDom2185 Mar 17, 2024
2f5e418
Fix type error
RichDom2185 Mar 17, 2024
25a7076
Fix type error
RichDom2185 Mar 17, 2024
645870a
Fix type error
RichDom2185 Mar 17, 2024
7a0361d
Fix type error
RichDom2185 Mar 17, 2024
ecde6d5
Fix type error
RichDom2185 Mar 17, 2024
1571dd5
Fix lint dependencies and config
RichDom2185 Mar 17, 2024
0008759
Fix type errors
RichDom2185 Mar 17, 2024
0940899
Fix type error
RichDom2185 Mar 17, 2024
c1e7888
Fix format
RichDom2185 Mar 17, 2024
7ea7c74
Fix type errors
RichDom2185 Mar 17, 2024
a808a74
Fix type error
RichDom2185 Mar 17, 2024
11ffac9
Fix type error
RichDom2185 Mar 17, 2024
b6f3cc3
Merge branch 'master' into ts-v5
RichDom2185 Mar 17, 2024
2df206a
Merge branch 'master' into ts-v5
RichDom2185 Mar 18, 2024
43a1985
Fix format
RichDom2185 Mar 18, 2024
2c89b35
Merge branch 'master' into ts-v5
RichDom2185 Mar 21, 2024
68ef514
Merge branch 'master' into ts-v5
RichDom2185 Mar 22, 2024
1b6b14d
Bump typescript to v5.4.3
RichDom2185 Mar 22, 2024
81ee811
Fix type errors
RichDom2185 Mar 22, 2024
b8bf143
Add some `as any` assertions
RichDom2185 Mar 22, 2024
c2e07cb
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Mar 26, 2024
2cc9656
Update lockfile post-merge
RichDom2185 Mar 26, 2024
ba93ac1
Merge branch 'master' into ts-v5
RichDom2185 Mar 31, 2024
7199cdc
Bump dependencies
RichDom2185 Mar 31, 2024
a1d0ef4
Merge branch 'master' into ts-v5
RichDom2185 Apr 10, 2024
f6dc07f
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Apr 13, 2024
86d8e9f
Update lockfile post-merge
RichDom2185 Apr 13, 2024
dcc1842
Create type-safe object helpers
RichDom2185 Apr 13, 2024
a93c30e
Fix type error
RichDom2185 Apr 13, 2024
dbedd6c
Fix type error
RichDom2185 Apr 13, 2024
8f57aa8
Fix type error
RichDom2185 Apr 13, 2024
27aabfc
Create more type helpers
RichDom2185 Apr 13, 2024
7a76650
Fix type error
RichDom2185 Apr 13, 2024
09a1974
Fix type error
RichDom2185 Apr 13, 2024
b512e16
Fix type error
RichDom2185 Apr 13, 2024
63e8707
Fix type error
RichDom2185 Apr 13, 2024
3ff06b2
Fix type errors
RichDom2185 Apr 13, 2024
e200e69
Fix type error
RichDom2185 Apr 13, 2024
57a3352
Fix type error
RichDom2185 Apr 13, 2024
53cd005
Fix type errors
RichDom2185 Apr 13, 2024
ee9e20d
Fix type error
RichDom2185 Apr 13, 2024
b3c6d6f
Merge branch 'master' into ts-v5
RichDom2185 Apr 13, 2024
6542acf
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Apr 13, 2024
48ee051
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Apr 13, 2024
0388549
Fix type error
RichDom2185 Apr 13, 2024
1060a52
Refactor filter logic to fix type error
RichDom2185 Apr 13, 2024
c4abc3d
Fix types
RichDom2185 Apr 13, 2024
84ca5a1
Merge branch 'master' into ts-v5
RichDom2185 Apr 13, 2024
a2cfc45
Address comments
RichDom2185 Apr 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"extends": ["react-app", "plugin:@typescript-eslint/recommended"],
"extends": [
// "eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
// "plugin:react/recommended",
// "plugin:react/jsx-runtime"
],
"plugins": ["simple-import-sort"],
"rules": {
"no-restricted-imports": [
Expand All @@ -18,6 +24,8 @@
]
}
],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-duplicate-enum-values": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/camelcase": "off",
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,16 @@
"@types/showdown": "^2.0.1",
"@types/uuid": "^9.0.0",
"@types/xml2js": "^0.4.11",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"babel-core": "6",
"babel-runtime": "^6.26.0",
"buffer": "^6.0.3",
"canvas": "^2.11.2",
"constants-browserify": "^1.0.0",
"coveralls": "^3.1.1",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"https-browserify": "^1.0.0",
"husky": "^9.0.0",
Expand All @@ -151,7 +154,7 @@
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"timers-browserify": "^2.0.12",
"typescript": "~4.9.0",
"typescript": "^5.4.3",
"url": "^0.11.1",
"webpack-bundle-analyzer": "^4.9.0"
},
Expand Down
12 changes: 9 additions & 3 deletions src/commons/XMLParser/XMLParserHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ const makeQuestions = (task: XmlParseStrTask): [Question[], number] => {

const makeMCQ = (problem: XmlParseStrCProblem, question: BaseQuestion): IMCQQuestion => {
const choicesVal: MCQChoice[] = [];
const solution = problem.SNIPPET ? problem.SNIPPET[0].SOLUTION : undefined;
const snippet = problem.SNIPPET;
// FIXME: I think `XmlParseStrCProblem` type definition is incorrect
// FIXME: Remove `as unknown as keyof typeof snippet` when fixed
// @ts-expect-error broken type definition to be fixed above
const solution = snippet ? snippet[0 as unknown as keyof typeof snippet].SOLUTION : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is used solely in MissionControl, which is marked as low priority/ deprecation. Consider starting an issue tracker for MissionControl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do that, though if we are removing it soon, the issue tracker might not be needed. Still waiting on Prof @martin-henz to reply on whether any of the profs still wants it to be kept.

let solutionVal = 0;
problem.CHOICE.forEach((choice: XmlParseStrProblemChoice, i: number) => {
choicesVal.push({
Expand Down Expand Up @@ -269,7 +273,8 @@ const exportLibrary = (library: Library) => {
name: library.external.name
}
}
};
// FIXME: Replace any with proper type
} as any;

if (library.external.symbols.length !== 0) {
/* tslint:disable:no-string-literal */
Expand Down Expand Up @@ -327,7 +332,8 @@ export const assessmentToXml = (
},
TEXT: question.content,
CHOICE: [] as any[]
};
// FIXME: Replace any with proper type
} as any;

if (question.library.chapter !== -1) {
/* tslint:disable:no-string-literal */
Expand Down
8 changes: 4 additions & 4 deletions src/commons/collabEditing/CollabEditingHelper.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import Constants from '../utils/Constants';

const protocolMap = {
const protocolMap = Object.freeze({
'http:': 'ws:',
'https:': 'wss:'
};
});

export function getSessionUrl(sessionId: string, ws?: boolean): string {
const url = new URL(sessionId, Constants.sharedbBackendUrl);
if (ws) {
url.protocol = protocolMap[url.protocol];
if (ws && Object.keys(protocolMap).includes(url.protocol)) {
url.protocol = protocolMap[url.protocol as keyof typeof protocolMap];
}
return url.toString();
}
Expand Down
22 changes: 15 additions & 7 deletions src/commons/documentation/Documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { deviceTypes } from 'src/features/remoteExecution/RemoteExecutionTypes';

import { externalLibraries } from '../application/types/ExternalTypes';

const externalLibrariesDocumentation = {};
type DocType = {
caption: string;
value: string;
meta: string;
docHTML?: string;
};

const externalLibrariesDocumentation: Record<string, DocType[]> = {};

const MAX_CAPTION_LENGTH = 27;

Expand All @@ -15,13 +22,14 @@ function shortenCaption(name: string): string {
return (name = name.substring(0, MAX_CAPTION_LENGTH - 3) + '...');
}

function mapExternalLibraryName(name: string) {
function mapExternalLibraryName(name: string): DocType {
if (name in SourceDocumentation.ext_lib) {
const key = name as keyof typeof SourceDocumentation.ext_lib;
return {
caption: shortenCaption(name),
value: name,
meta: SourceDocumentation.ext_lib[name].meta,
docHTML: SourceDocumentation.ext_lib[name].description
caption: shortenCaption(key),
value: key,
meta: SourceDocumentation.ext_lib[key].meta,
docHTML: SourceDocumentation.ext_lib[key].description
};
} else {
return {
Expand All @@ -42,7 +50,7 @@ for (const deviceType of deviceTypes) {
deviceType.internalFunctions.map(mapExternalLibraryName);
}

const builtinDocumentation = {};
const builtinDocumentation: Record<string, DocType[]> = {};

Object.entries(SourceDocumentation.builtins).forEach((chapterDoc: any) => {
const [chapter, docs] = chapterDoc;
Expand Down
2 changes: 1 addition & 1 deletion src/commons/editingWorkspace/EditingWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ const EditingWorkspace: React.FC<EditingWorkspaceProps> = props => {
};

function uniq(a: string[]) {
const seen = {};
const seen: Record<string, boolean> = {};
return a.filter(item => (seen.hasOwnProperty(item) ? false : (seen[item] = true)));
}

Expand Down
3 changes: 2 additions & 1 deletion src/commons/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IAceEditor } from 'react-ace/lib/types';
import { HotKeys } from 'react-hotkeys';
import { EditorBinding } from '../WorkspaceSettingsContext';
import { getModeString, selectMode } from '../utils/AceHelper';
import { objectEntries } from '../utils/TypeHelper';
import { KeyFunction, keyBindings } from './EditorHotkeys';
import { AceMouseEvent, HighlightedLines, Position } from './EditorTypes';

Expand Down Expand Up @@ -537,7 +538,7 @@ const EditorBase = React.memo((props: EditorProps & LocalStateProps) => {
]
);

aceEditorProps.commands = Object.entries(keyHandlers)
aceEditorProps.commands = objectEntries(keyHandlers)
.filter(([_, exec]) => exec)
.map(([name, exec]) => ({ name, bindKey: keyBindings[name], exec: exec! }));

Expand Down
25 changes: 15 additions & 10 deletions src/commons/editor/tabs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export const getShortestUniqueFilePaths = (originalFilePaths: string[]): string[
// Split each original file path into path segments and store the mapping from file
// path to path segments for O(1) lookup. Since we only deal with the BrowserFS file
// system, the path separator will always be '/'.
const filePathSegments: Record<string, string[]> = originalFilePaths.reduce(
const filePathSegments: Record<string, string[]> = originalFilePaths.reduce<
typeof filePathSegments
>(
(segments, filePath) => ({
...segments,
// It is necessary to remove empty segments to deal with the very first '/' in
Expand All @@ -48,15 +50,18 @@ export const getShortestUniqueFilePaths = (originalFilePaths: string[]): string[
// to any original file path which transforms into it.
const shortenedToOriginalFilePaths: Record<string, string[]> = Object.entries(
filePathSegments
).reduce((filePaths, [originalFilePath, filePathSegments]) => {
// Note that if there are fewer path segments than the number being sliced,
// all of the path segments will be returned without error.
const shortenedFilePath = '/' + filePathSegments.slice(-numOfPathSegments).join('/');
return {
...filePaths,
[shortenedFilePath]: (filePaths[shortenedFilePath] ?? []).concat(originalFilePath)
};
}, {});
).reduce<typeof shortenedToOriginalFilePaths>(
(filePaths, [originalFilePath, filePathSegments]) => {
// Note that if there are fewer path segments than the number being sliced,
// all of the path segments will be returned without error.
const shortenedFilePath = '/' + filePathSegments.slice(-numOfPathSegments).join('/');
return {
...filePaths,
[shortenedFilePath]: (filePaths[shortenedFilePath] ?? []).concat(originalFilePath)
};
},
{}
);
// Each shortened file path that only has a single corresponding original file
// path is added to the unique shortened file paths record and their entry in
// the file path segments record is removed to prevent further processing.
Expand Down
2 changes: 1 addition & 1 deletion src/commons/sagas/WorkspaceSaga/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ export default function* WorkspaceSaga(): SagaIterator {
yield call([CseMachine, CseMachine.clear]);
const globals: Array<[string, any]> = action.payload.library.globals as Array<[string, any]>;
for (const [key, value] of globals) {
window[key] = value;
window[key as any] = value;
}
yield put(
actions.endClearContext(
Expand Down
2 changes: 1 addition & 1 deletion src/commons/sideContent/SideContentHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const requireProvider = (x: string) => {
};

if (!(x in exports)) throw new Error(`Dynamic require of ${x} is not supported`);
return exports[x];
return exports[x as keyof typeof exports] as any;
};

type RawTab = (provider: ReturnType<typeof requireProvider>) => { default: ModuleSideContent };
Expand Down
4 changes: 2 additions & 2 deletions src/commons/sideContent/content/SideContentContestVoting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ const SideContentContestVoting: React.FC<SideContentContestVotingProps> = ({
[currentDraggedItem]
);

const contestEntryRefs = useRef({});
const tierContainerRefs = useRef({});
const contestEntryRefs = useRef<Record<number, HTMLDivElement | null>>({});
const tierContainerRefs = useRef<Record<number, HTMLDivElement | null>>({});

const tierBoard = useMemo(() => {
return TIERS.map((tier, index) => (
Expand Down
10 changes: 6 additions & 4 deletions src/commons/utils/DisplayBufferService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ class BufferService {

public attachConsole(workspaceLocation: WorkspaceLocation): () => void {
const bufferCallback = (log: string) => this.push(log, workspaceLocation);
const defaultConsole = {};
const defaultConsole: Record<string, any> = {};
Object.entries(consoleOverloads).forEach(([method, overload]) => {
defaultConsole[method] = console[method];
console[method] = overload(bufferCallback);
const key = method as keyof typeof consoleOverloads;
defaultConsole[method] = console[key];
console[key] = overload(bufferCallback);
});

return () => {
Object.entries(consoleOverloads).forEach(([method]) => {
console[method] = defaultConsole[method];
const key = method as keyof typeof consoleOverloads;
console[key] = defaultConsole[key];
});
};
}
Expand Down
8 changes: 4 additions & 4 deletions src/commons/utils/JsSlangHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export function makeElevatedContext(context: Context) {
if (prop === 'head') {
return fakeFrame;
}
return target[prop];
return target[prop as keyof typeof target];
}
});

Expand All @@ -189,7 +189,7 @@ export function makeElevatedContext(context: Context) {
if (prop === '0') {
return proxyGlobalEnv;
}
return target[prop];
return target[prop as keyof typeof target];
}
});

Expand All @@ -198,7 +198,7 @@ export function makeElevatedContext(context: Context) {
if (prop === 'environments') {
return proxyEnvs;
}
return target[prop];
return target[prop as keyof typeof target];
}
});

Expand All @@ -210,7 +210,7 @@ export function makeElevatedContext(context: Context) {
case 'runtime':
return proxyRuntime;
default:
return target[prop];
return target[prop as keyof typeof target];
}
}
});
Expand Down
49 changes: 49 additions & 0 deletions src/commons/utils/TypeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,32 @@ export type KeysOfType<O, T> = {
[K in keyof O]: O[K] extends T ? K : never;
}[keyof O];

/**
* Does union(keyof <member>) for each member of T. This is unlike
* `keyof T` which would give keyof(union(<member>)).
* @param T - The union type to extract keys from
*/
export type DistributedKeyOf<T extends Record<any, any>> = T extends any ? keyof T : never;

/**
* Generates a "set difference" of two types, keeping the properties of T that are not
* present in S.
* @param T - The type to extract keys from
* @param S - The type to compare against
*/
export type Diff<T extends Record<any, any>, S extends Record<any, any>> = Pick<
T,
Exclude<keyof T, keyof S>
>;

/**
* Merges two types together, keeping the properties of T and adding the
* properties of U that are not present in T.
* @param T - The first type
* @param U - The second type (also "universal" set of properties to add to T)
*/
export type Merge<T extends Record<any, any>, U extends Record<any, any>> = T & Diff<U, T>;

// Adapted from https://github.com/piotrwitek/typesafe-actions/blob/a1fe54bb150ac1b935bb9ca78361d2d024d2efaf/src/type-helpers.ts#L117-L130
export type ActionType<T extends Record<string, any>> = {
[k in keyof T]: ReturnType<T[k]>;
Expand Down Expand Up @@ -100,3 +126,26 @@ export const assertType =
// Keep the original type as inferred by TS
): T =>
obj;

/**
* Type safe `Object.keys`
*/
export function objectKeys<T extends string | number | symbol>(obj: Record<T, any>): T[] {
return Object.keys(obj) as T[];
}

/**
* Type safe `Object.values`
*/
export function objectValues<T>(obj: Record<any, T>) {
return Object.values(obj) as T[];
}

/**
* Type safe `Object.entries`
*/
export function objectEntries<K extends string | number | symbol, V>(
obj: Partial<Record<K, V>>
): [K, V][] {
return Object.entries(obj) as [K, V][];
}
Loading