From 511b1a1e24eed0fbfd677a9ed46a5fd55df8182f Mon Sep 17 00:00:00 2001 From: "Felix W. Dekker" Date: Mon, 21 Oct 2019 17:07:16 +0200 Subject: [PATCH] Enable strict type checking --- js/commands.ts | 10 +++++----- js/extensions.d.ts | 2 +- js/extensions.ts | 6 +++--- js/fs.ts | 41 +++++++++++++++++++++++++++++------------ js/shared.ts | 12 +++++++++--- js/terminal.ts | 13 +++++++------ tsconfig.json | 1 + 7 files changed, 55 insertions(+), 30 deletions(-) diff --git a/js/commands.ts b/js/commands.ts index ea6e9b4..81d5d1c 100644 --- a/js/commands.ts +++ b/js/commands.ts @@ -6,7 +6,7 @@ import {Terminal} from "./terminal.js"; export class Commands { private readonly terminal: Terminal; private readonly fileSystem: FileSystem; - private readonly commands: object; + private readonly commands: { [key: string]: Command }; constructor(terminal: Terminal, fileSystem: FileSystem) { @@ -134,7 +134,7 @@ export class Commands { } - parse(input: string): string { + execute(input: string): string { const args = new InputArgs(input); if (args.command.trim() === "") @@ -296,7 +296,7 @@ class Command { class InputArgs { readonly command: string; - private readonly _options: object; + private readonly _options: { [key: string]: string }; private readonly _args: string[]; @@ -353,7 +353,7 @@ class InputArgs { } - getArg(index: number, def: string = undefined): string { + getArg(index: number, def: string | undefined = undefined): string { return (def === undefined) ? this._args[index] : (this.hasArg(index) ? this._args[index] : def); @@ -364,7 +364,7 @@ class InputArgs { } - getOption(key: string, def: string = undefined) { + getOption(key: string, def: string | undefined = undefined) { return (def === undefined) ? this._options[key] : (this.hasOption(key) ? this._options[key] : def); diff --git a/js/extensions.d.ts b/js/extensions.d.ts index 4a6c08f..4c07a41 100644 --- a/js/extensions.d.ts +++ b/js/extensions.d.ts @@ -3,5 +3,5 @@ interface String { } interface Array { - sortAlphabetically(transform: (element: T) => string); + sortAlphabetically(transform: (element: T) => string): T[]; } diff --git a/js/extensions.ts b/js/extensions.ts index 85be948..747e00f 100644 --- a/js/extensions.ts +++ b/js/extensions.ts @@ -9,7 +9,7 @@ String.prototype.trimLines = function (): string { }; String.prototype.replaceAll = function (regex, replacement) { - let string = this; + let string = this.toString(); while (regex.test(string)) string = string.replace(regex, replacement); @@ -19,10 +19,10 @@ String.prototype.replaceAll = function (regex, replacement) { interface Array { - sortAlphabetically(transform: (element: T) => string); + sortAlphabetically(transform: (element: T) => string): T[]; } -Array.prototype.sortAlphabetically = function (transform = (x) => x) { +Array.prototype.sortAlphabetically = function (transform: (_: any) => string = (it) => it) { return this.sort((a, b) => { const aName = transform(a).toLowerCase(); const bName = transform(b).toLowerCase(); diff --git a/js/fs.ts b/js/fs.ts index 08f878a..a2f5618 100644 --- a/js/fs.ts +++ b/js/fs.ts @@ -28,6 +28,7 @@ export class FileSystem { }), "resume.pdf": new UrlFile("https://static.fwdekker.com/misc/resume.pdf") }); + this.files = this.root; } @@ -62,8 +63,8 @@ export class FileSystem { } - private executeForEach(inputs: string[], fun: (string) => string): string { - const outputs = []; + private executeForEach(inputs: string[], fun: (_: string) => string): string { + const outputs: string[] = []; inputs.forEach(input => { const output = fun(input); @@ -161,9 +162,15 @@ export class FileSystem { let targetNode; let targetName; if (destinationTailNode === undefined) { + if (!(destinationHeadNode instanceof Directory)) + return `The path '${destinationPath.head}' does not point to a directory`; + targetNode = destinationHeadNode; targetName = destinationPath.tail; } else { + if (!(destinationTailNode instanceof Directory)) + return `The path '${destinationPath.tail}' does not point to a directory`; + targetNode = destinationTailNode; targetName = sourcePath.tail; } @@ -192,7 +199,7 @@ export class FileSystem { return `'${path.path}' is not a directory`; const dirList = [new Directory({}).nameString("."), new Directory({}).nameString("..")]; - const fileList = []; + const fileList: string[] = []; const nodes = node.nodes; Object.keys(nodes) @@ -272,9 +279,15 @@ export class FileSystem { let targetNode; let targetName; if (destinationTailNode === undefined) { + if (!(destinationHeadNode instanceof Directory)) + return `The path '${destinationPath.head}' does not point to a directory`; + targetNode = destinationHeadNode; targetName = destinationPath.tail; } else { + if (!(destinationTailNode instanceof Directory)) + return `The path '${destinationPath.tail}' does not point to a directory`; + targetNode = destinationTailNode; targetName = sourcePath.tail; } @@ -403,7 +416,7 @@ export class Path { readonly tail: string; - constructor(currentPath: string, relativePath: string = undefined) { + constructor(currentPath: string, relativePath: string | undefined = undefined) { let path; if (relativePath === undefined) path = currentPath; @@ -435,26 +448,29 @@ export abstract class Node { abstract nameString(name: string): string; - abstract visit(fun: (node: Node) => void, pre: (node: Node) => void, post: (node: Node) => void); + abstract visit(fun: (node: Node) => void, pre: (node: Node) => void, post: (node: Node) => void): void; } export class Directory extends Node { - private readonly _nodes: object; + private readonly _nodes: { [key: string]: Node }; // noinspection TypeScriptFieldCanBeMadeReadonly: False positive private _parent: Directory; - constructor(nodes: object = {}) { + constructor(nodes: { [key: string]: Node } = {}) { super(); this._parent = this; this._nodes = nodes; - Object.keys(this._nodes).forEach(name => this._nodes[name]._parent = this); + Object.values(this._nodes) + .forEach(node => { + if (node instanceof Directory) node._parent = this; + }); } - get nodes(): object { + get nodes(): { [key: string]: Node } { return Object.assign({}, this._nodes); } @@ -488,6 +504,9 @@ export class Directory extends Node { removeNode(nodeOrName: Node | string) { if (nodeOrName instanceof Node) { const name = Object.keys(this._nodes).find(key => this._nodes[key] === nodeOrName); + if (name === undefined) + throw `Could not remove node '${nodeOrName}'.`; + delete this._nodes[name]; } else { delete this._nodes[name]; @@ -496,9 +515,7 @@ export class Directory extends Node { copy(): Directory { - const copy = new Directory(this.nodes); - copy._parent = undefined; - return copy; + return new Directory(this.nodes); } nameString(name: string): string { diff --git a/js/shared.ts b/js/shared.ts index d62fe55..9a2e136 100644 --- a/js/shared.ts +++ b/js/shared.ts @@ -26,10 +26,16 @@ export function moveCaretToEndOf(element: Node) { range.collapse(false); const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); + if (selection !== null) { + selection.removeAllRanges(); + selection.addRange(range); + } } export function q(query: string): HTMLElement { - return document.querySelector(query); + const element = document.querySelector(query); + if (!(element instanceof HTMLElement)) + throw "Could not find element `query`."; + + return element; } diff --git a/js/terminal.ts b/js/terminal.ts index c300d2f..cd15c3c 100644 --- a/js/terminal.ts +++ b/js/terminal.ts @@ -13,7 +13,7 @@ export class Terminal { private readonly fileSystem: FileSystem; private readonly commands: Commands; - private _currentUser: string; + private _currentUser: string | undefined; private isLoggedIn: boolean; @@ -64,7 +64,7 @@ export class Terminal { this.prefixDiv.innerHTML = prefixText; } - get currentUser(): string { + get currentUser(): string | undefined { return this._currentUser; } @@ -149,7 +149,7 @@ export class Terminal { this.outputText += `${this.prefixText}${input}\n`; this.inputHistory.addEntry(input); - const output = this.commands.parse(input.trim()); + const output = this.commands.execute(input.trim()); if (output !== "") this.outputText += output + `\n`; } @@ -162,7 +162,7 @@ export class Terminal { this.input.focus(); } - private onkeypress(event) { + private onkeypress(event: KeyboardEvent) { switch (event.key.toLowerCase()) { case "enter": this.processInput(this.inputText.replaceAll(/ /, " ")); @@ -171,7 +171,7 @@ export class Terminal { } } - private onkeydown(event) { + private onkeydown(event: KeyboardEvent) { switch (event.key.toLowerCase()) { case "arrowup": this.inputText = this.inputHistory.previousEntry(); @@ -200,7 +200,8 @@ class InputHistory { constructor() { - this.clear(); + this.history = []; + this.index = -1; } diff --git a/tsconfig.json b/tsconfig.json index fd7f614..692f88f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "module": "esnext", "target": "es2019", "sourceMap": true, + "strict": true, "outDir": "./build/js" }, "files": [