Skip to content

Commit e2d0afc

Browse files
author
Michael Mrowetz
committed
added ts strict mode and fixed resulting issues
1 parent b43e4de commit e2d0afc

23 files changed

+171
-132
lines changed

Diff for: src/ts/file-reader.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ zip.useWebWorkers = false;
88
/** handle client side file upload */
99
export function readFile(file: File,
1010
fileName: string,
11-
callback: (e: Error, har?: Har) => void,
11+
callback: (e: Error | null, har?: Har) => void,
1212
onProgress?: (progress: number) => void) {
1313
if (!file) {
1414
return callback(new Error("Failed to load HAR file"));
@@ -24,7 +24,7 @@ export function readFile(file: File,
2424
}
2525

2626
/** start reading the file */
27-
const extension = fileName.match(/\.[0-9a-z]+$/i)[0];
27+
const extension = (fileName.match(/\.[0-9a-z]+$/i) || [])[0];
2828
if ([".zhar", ".zip"].indexOf(extension) !== -1) {
2929
/** zhar */
3030
zip.createReader(new zip.BlobReader(file), (zipReader) => {

Diff for: src/ts/helpers/dom.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function removeClass<T extends Element>(el: T, className: string): T {
2525
classList.remove(className);
2626
} else {
2727
// IE doesn't support classList in SVG
28-
el.setAttribute("class", el.getAttribute("class")
28+
el.setAttribute("class", (el.getAttribute("class") || "")
2929
.replace(new RegExp("(\\s|^)" + className + "(\\s|$)", "g"), "$2"));
3030
}
3131
return el;
@@ -44,9 +44,9 @@ export function getParentByClassName(base: Element, className: string) {
4444
if (base.classList.contains(className)) {
4545
return base;
4646
}
47-
base = base.parentElement;
47+
base = base.parentElement as Element;
4848
}
49-
return undefined;
49+
return null;
5050
}
5151

5252
/**
@@ -55,7 +55,7 @@ export function getParentByClassName(base: Element, className: string) {
5555
*/
5656
export function removeChildren<T extends Element>(el: T): T {
5757
while (el.hasChildNodes()) {
58-
el.removeChild(el.lastChild);
58+
el.removeChild(el.lastChild as Element);
5959
}
6060
return el;
6161
}
@@ -64,7 +64,7 @@ export function removeChildren<T extends Element>(el: T): T {
6464
* Get last element of `NodeList`
6565
* @param list NodeListOf e.g. return value of `getElementsByClassName`
6666
*/
67-
export function getLastItemOfNodeList<T extends Node>(list: NodeListOf<T>) {
67+
export function getLastItemOfNodeList<T extends Node>(list: NodeListOf<T> | null) {
6868
if (!list || list.length === 0) {
6969
return undefined;
7070
}
@@ -96,7 +96,7 @@ export function safeSetAttribute(el: HTMLElement | SVGElement, name: string, val
9696
console.warn(new Error(`Trying to set non-existing attribute ` +
9797
`${name} = ${value} on a <${el.tagName.toLowerCase()}>.`));
9898
}
99-
el.setAttributeNS(null, name, value);
99+
el.setAttributeNS("", name, value);
100100
}
101101

102102
/** Sets multiple CSS style properties, but only if property exists on `el` */

Diff for: src/ts/helpers/misc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99
function parseUrl(url: string) {
1010
const pattern = RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
11-
const matches = url.match(pattern);
11+
const matches = url.match(pattern) || [];
1212
return {
1313
authority: matches[4],
1414
fragment: matches[9],

Diff for: src/ts/helpers/parse.ts

+36-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ChartRenderOption } from "../typing/options";
22
import { roundNumber } from "./misc";
33

4+
export type MaybeStringOrNumber = string | number | undefined | null;
5+
46
/**
57
* Type safe and null safe way to transform, filter and format an input value, e.g. parse a Date from a string,
68
* rejecting invalid dates, and formatting it as a localized string. If the input value is undefined, or the parseFn
@@ -10,9 +12,9 @@ import { roundNumber } from "./misc";
1012
* @param formatFn an optional function to format the parsed input value.
1113
* @returns {string} a formatted string representation of the input, or undefined.
1214
*/
13-
export function parseAndFormat<S, T>(input: S,
15+
export function parseAndFormat<S, T>(input: S | undefined,
1416
parseFn: ((_: S) => T),
15-
formatFn: ((_: T) => string) = toString): string {
17+
formatFn: ((_: T) => string | undefined) = toString): string | undefined {
1618
if (input === undefined) {
1719
return undefined;
1820
}
@@ -27,33 +29,39 @@ function toString<T>(source: T): string {
2729
if (typeof source["toString"] === "function") {
2830
return source.toString();
2931
} else {
30-
throw TypeError("Can't convert type ${typeof source} to string");
32+
throw TypeError(`Can't convert type ${typeof source} to string`);
3133
}
3234
}
3335

34-
export function parseNonEmpty(input: string): string {
36+
export function parseNonEmpty(input: string): string | undefined {
3537
return input.trim().length > 0 ? input : undefined;
3638
}
3739

38-
export function parseDate(input: string): Date {
40+
export function parseDate(input: string): Date | undefined {
3941
const date = new Date(input);
4042
if (isNaN(date.getTime())) {
4143
return undefined;
4244
}
4345
return date;
4446
}
4547

46-
export function parseNonNegative(input: string | number): number {
48+
export function parseNonNegative(input: MaybeStringOrNumber): number | undefined {
49+
if (input === undefined || input === null) {
50+
return undefined;
51+
}
4752
const filter = (n) => (n >= 0);
4853
return parseToNumber(input, filter);
4954
}
5055

51-
export function parsePositive(input: string | number): number {
56+
export function parsePositive(input: MaybeStringOrNumber): number | undefined {
57+
if (input === undefined || input === null) {
58+
return undefined;
59+
}
5260
const filter = (n) => (n > 0);
5361
return parseToNumber(input, filter);
5462
}
5563

56-
function parseToNumber(input: string | number, filterFn: (_: number) => boolean): number {
64+
function parseToNumber(input: string | number, filterFn: (_: number) => boolean): number | undefined {
5765
const filter = (n: number) => filterFn(n) ? n : undefined;
5866

5967
if (typeof input === "string") {
@@ -66,15 +74,19 @@ function parseToNumber(input: string | number, filterFn: (_: number) => boolean)
6674
return filter(input);
6775
}
6876

69-
export function formatMilliseconds(millis: number): string {
70-
return `${roundNumber(millis, 3)} ms`;
77+
export function formatMilliseconds(millis: number | undefined): string | undefined {
78+
return (millis !== undefined) ? `${roundNumber(millis, 3)} ms` : undefined;
7179
}
7280

7381
const secondsPerMinute = 60;
7482
const secondsPerHour = 60 * secondsPerMinute;
7583
const secondsPerDay = 24 * secondsPerHour;
7684

77-
export function formatSeconds(seconds: number): string {
85+
export function formatSeconds(seconds: number | undefined): string | undefined {
86+
if (seconds === undefined) {
87+
return undefined;
88+
}
89+
7890
const raw = `${roundNumber(seconds, 3)} s`;
7991
if (seconds > secondsPerDay) {
8092
return `${raw} (~${roundNumber(seconds / secondsPerDay, 0)} days)`;
@@ -88,14 +100,17 @@ export function formatSeconds(seconds: number): string {
88100
return raw;
89101
}
90102

91-
export function formatDateLocalized(date: Date): string {
92-
return `${date.toUTCString()}<br/>(local time: ${date.toLocaleString()})`;
103+
export function formatDateLocalized(date: Date | undefined): string | undefined {
104+
return (date !== undefined) ? `${date.toUTCString()}<br/>(local time: ${date.toLocaleString()})` : undefined;
93105
}
94106

95107
const bytesPerKB = 1024;
96108
const bytesPerMB = 1024 * bytesPerKB;
97109

98-
export function formatBytes(bytes: number): string {
110+
export function formatBytes(bytes: number | undefined): string {
111+
if (bytes === undefined) {
112+
return "";
113+
}
99114
const raw = `${bytes} bytes`;
100115
if (bytes >= bytesPerMB) {
101116
return `${raw} (~${roundNumber(bytes / bytesPerMB, 1)} MB)`;
@@ -124,8 +139,8 @@ const htmlChars = new RegExp(Object.keys(htmlCharMap).join("|"), "g");
124139
* Escapes unsafe characters in a string to render safely in HTML
125140
* @param {string} unsafe - string to be rendered in HTML
126141
*/
127-
export function escapeHtml(unsafe: string | number | boolean = ""): string {
128-
if (unsafe === null) {
142+
export function escapeHtml(unsafe: MaybeStringOrNumber | boolean = ""): string {
143+
if (unsafe === null || unsafe === undefined) {
129144
return ""; // See https://github.com/micmro/PerfCascade/issues/217
130145
}
131146
if (typeof unsafe !== "string") {
@@ -149,7 +164,7 @@ export function sanitizeUrlForLink(unsafeUrl: string) {
149164
if (cleaned.indexOf("http://") === 0 || cleaned.indexOf("https://") === 0) {
150165
return cleaned;
151166
}
152-
// tslint:disable-next-line:no-console
167+
// tslint:disable-next-line:no-console
153168
console.warn("skipped link, due to potentially unsafe url", unsafeUrl);
154169
return "";
155170
}
@@ -163,7 +178,7 @@ export function sanitizeAlphaNumeric(unsafe: string | number) {
163178
}
164179

165180
/** Ensures `input` is casted to `number` */
166-
export function toInt(input: string | number): number {
181+
export function toInt(input: MaybeStringOrNumber): number | undefined {
167182
if (typeof input === "number") {
168183
return input;
169184
} else if (typeof input === "string") {
@@ -176,10 +191,11 @@ export function toInt(input: string | number): number {
176191
/** Validates the `ChartOptions` attributes types */
177192
export function validateOptions(options: ChartRenderOption): ChartRenderOption {
178193
const validateInt = (name: keyof ChartRenderOption) => {
179-
options[name] = toInt(options[name] as any);
180-
if (options[name] === undefined) {
194+
const val = toInt(options[name] as any);
195+
if (val === undefined) {
181196
throw TypeError(`option "${name}" needs to be a number`);
182197
}
198+
options[name] = val;
183199
};
184200
const ensureBoolean = (name: keyof ChartRenderOption) => {
185201
options[name] = !!options[name];

Diff for: src/ts/helpers/svg.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const getTestSVGEl = (() => {
122122
// debounced time-deleayed cleanup, so the element can be re-used in tight loops
123123
clearTimeout(removeSvgTestElTimeout);
124124
removeSvgTestElTimeout = setTimeout(() => {
125-
svgTestEl.parentNode.removeChild(svgTestEl);
125+
(svgTestEl.parentNode as Node).removeChild(svgTestEl);
126126
}, 500);
127127

128128
return svgTestEl;
@@ -136,7 +136,7 @@ const getTestSVGEl = (() => {
136136
* @returns number
137137
*/
138138
export function getNodeTextWidth(textNode: SVGTextElement, skipClone: boolean = false): number {
139-
if (textNode.textContent.length === 0) {
139+
if ((textNode.textContent || "").length === 0) {
140140
return 0;
141141
}
142142
const tmp = getTestSVGEl();

Diff for: src/ts/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function PerfCascade(waterfallDocsData: WaterfallDocs, chartOptions: Partial<Cha
4444

4545
// page update behaviour
4646
paging.onPageUpdate((_pageIndex, pageDoc) => {
47-
const el = doc.parentElement;
47+
const el = doc.parentElement as HTMLElement;
4848
const newDoc = createWaterfallSvg(pageDoc, options);
4949
el.replaceChild(newDoc, doc);
5050
doc = newDoc;

Diff for: src/ts/paging/paging.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default class Paging {
6262
* @param {OnPagingCb} cb
6363
* @returns number - index of the callback
6464
*/
65-
public onPageUpdate(cb: OnPagingCb): number {
65+
public onPageUpdate(cb: OnPagingCb): number | undefined {
6666
if (this.getPageCount() > 1) {
6767
return this.onPageUpdateCbs.push(cb);
6868
}

Diff for: src/ts/transformers/extract-details-keys.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ import {
55
formatDateLocalized,
66
formatMilliseconds,
77
formatSeconds,
8+
MaybeStringOrNumber,
89
parseAndFormat,
910
parseDate,
1011
parseNonEmpty,
1112
parseNonNegative,
1213
parsePositive,
1314
} from "../helpers/parse";
14-
import { KvTuple } from "../typing/waterfall";
15+
import { KvTuple, SafeKvTuple } from "../typing/waterfall";
1516
import { flattenKvTuple } from "./helpers";
1617

17-
const byteSizeProperty = (title: string, input: string |  number): KvTuple => {
18+
const byteSizeProperty = (title: string, input: MaybeStringOrNumber): KvTuple => {
1819
return [title, parseAndFormat(input, parsePositive, formatBytes)];
1920
};
20-
const countProperty = (title: string, input: string |  number): KvTuple => {
21+
const countProperty = (title: string, input: MaybeStringOrNumber): KvTuple => {
2122
return [title, parseAndFormat(input, parsePositive)];
2223
};
2324

@@ -26,7 +27,7 @@ const notEmpty = (kv: KvTuple) => {
2627
return kv.length > 1 && kv[1] !== undefined && kv[1] !== "";
2728
};
2829

29-
function parseGeneralDetails(entry: Entry, startRelative: number, requestID: number): KvTuple[] {
30+
function parseGeneralDetails(entry: Entry, startRelative: number, requestID: number): SafeKvTuple[] {
3031
return ([
3132
["Request Number", `#${requestID}`],
3233
["Started", new Date(entry.startedDateTime).toLocaleString() + ((startRelative > 0) ?
@@ -55,10 +56,10 @@ function parseGeneralDetails(entry: Entry, startRelative: number, requestID: num
5556
byteSizeProperty("Minify Save", entry._minify_save),
5657
byteSizeProperty("Image Total", entry._image_total),
5758
byteSizeProperty("Image Save", entry._image_save),
58-
] as KvTuple[]).filter(notEmpty);
59+
] as KvTuple[]).filter(notEmpty) as SafeKvTuple[];
5960
}
6061

61-
function parseRequestDetails(harEntry: Entry): KvTuple[] {
62+
function parseRequestDetails(harEntry: Entry): SafeKvTuple[] {
6263
const request = harEntry.request;
6364
const stringHeader = (name: string): KvTuple[] => getHeaders(request.headers, name);
6465

@@ -81,10 +82,10 @@ function parseRequestDetails(harEntry: Entry): KvTuple[] {
8182
stringHeader("If-Unmodified-Since"),
8283
countProperty("Querystring parameters count", request.queryString.length),
8384
countProperty("Cookies count", request.cookies.length),
84-
]).filter(notEmpty);
85+
]).filter(notEmpty) as SafeKvTuple[];
8586
}
8687

87-
function parseResponseDetails(entry: Entry): KvTuple[] {
88+
function parseResponseDetails(entry: Entry): SafeKvTuple[] {
8889
const response = entry.response;
8990
const content = response.content;
9091
const headers = response.headers;
@@ -138,20 +139,20 @@ function parseResponseDetails(entry: Entry): KvTuple[] {
138139
stringHeader("Timing-Allow-Origin"),
139140
["Redirect URL", parseAndFormat(response.redirectURL, parseNonEmpty)],
140141
["Comment", parseAndFormat(response.comment, parseNonEmpty)],
141-
]).filter(notEmpty);
142+
]).filter(notEmpty) as SafeKvTuple[];
142143
}
143144

144-
function parseTimings(entry: Entry, start: number, end: number): KvTuple[] {
145+
function parseTimings(entry: Entry, start: number, end: number): SafeKvTuple[] {
145146
const timings = entry.timings;
146147
const optionalTiming = (timing?: number) => parseAndFormat(timing, parseNonNegative, formatMilliseconds);
147148
const total = (typeof start !== "number" || typeof end !== "number") ? undefined : (end - start);
148149

149150
let connectVal = optionalTiming(timings.connect);
150-
if (timings.ssl > 0) {
151+
if (timings.ssl && timings.ssl > 0 && timings.connect) {
151152
// SSL time is also included in the connect field (to ensure backward compatibility with HAR 1.1).
152153
connectVal = `${connectVal} (without TLS: ${optionalTiming(timings.connect - timings.ssl)})`;
153154
}
154-
return [
155+
return ([
155156
["Total", formatMilliseconds(total)],
156157
["Blocked", optionalTiming(timings.blocked)],
157158
["DNS", optionalTiming(timings.dns)],
@@ -160,7 +161,7 @@ function parseTimings(entry: Entry, start: number, end: number): KvTuple[] {
160161
["Send", formatMilliseconds(timings.send)],
161162
["Wait", formatMilliseconds(timings.wait)],
162163
["Receive", formatMilliseconds(timings.receive)],
163-
];
164+
] as KvTuple[]).filter(notEmpty) as SafeKvTuple[];
164165
}
165166

166167
/**
@@ -176,9 +177,9 @@ export function getKeys(entry: Entry, requestID: number, startRelative: number,
176177
return {
177178
general: parseGeneralDetails(entry, startRelative, requestID),
178179
request: parseRequestDetails(entry),
179-
requestHeaders: requestHeaders.map(headerToKvTuple),
180+
requestHeaders: requestHeaders.map(headerToKvTuple).filter(notEmpty) as SafeKvTuple[],
180181
response: parseResponseDetails(entry),
181-
responseHeaders: responseHeaders.map(headerToKvTuple),
182+
responseHeaders: responseHeaders.map(headerToKvTuple).filter(notEmpty) as SafeKvTuple[],
182183
timings: parseTimings(entry, startRelative, endRelative),
183184
};
184185
}

Diff for: src/ts/transformers/har-heuristics.ts

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ function isSecure(entry: Entry) {
6262
}
6363

6464
function isPush(entry: Entry): boolean {
65+
if (entry._was_pushed === undefined || entry._was_pushed === null) {
66+
return false;
67+
}
6568
function toInt(input: string | number): number {
6669
if (typeof input === "string") {
6770
return parseInt(input, 10);

0 commit comments

Comments
 (0)