lighthouse 9.5.0-dev.20221115 → 9.5.0-dev.20221117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/cli-flags.js CHANGED
@@ -351,7 +351,7 @@ function getFlags(manualArgv, options = {}) {
351
351
  // Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
352
352
  // so for now cast to add yarg's camelCase properties to type.
353
353
  const argv = /** @type {Awaited<typeof parser.argv>} */ (parser.argv);
354
- const cliFlags = /** @type {typeof argv & CamelCasify<typeof argv>} */ (argv);
354
+ const cliFlags = /** @type {typeof argv & LH.Util.CamelCasify<typeof argv>} */ (argv);
355
355
 
356
356
  // yargs will return `undefined` for options that have a `coerce` function but
357
357
  // are not actually present in the user input. Instead of passing properties
package/cli/printer.js CHANGED
@@ -13,7 +13,7 @@ import log from 'lighthouse-logger';
13
13
  * 'json': JSON formatted results
14
14
  * 'html': An HTML report
15
15
  * 'csv': CSV formatted results
16
- * @type {SelfMap<LH.OutputMode>}
16
+ * @type {LH.Util.SelfMap<LH.OutputMode>}
17
17
  */
18
18
  const OutputMode = {
19
19
  json: 'json',
@@ -174,7 +174,7 @@ async function begin() {
174
174
  // Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
175
175
  // so for now cast to add yarg's camelCase properties to type.
176
176
  const argv =
177
- /** @type {Awaited<typeof rawArgv> & CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);
177
+ /** @type {Awaited<typeof rawArgv> & LH.Util.CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);
178
178
 
179
179
  const jobs = Number.isFinite(argv.jobs) ? argv.jobs : undefined;
180
180
  const retries = Number.isFinite(argv.retries) ? argv.retries : undefined;
@@ -138,7 +138,7 @@ class RenderBlockingResources extends Audit {
138
138
  const simulator = await LoadSimulator.request(simulatorData, context);
139
139
  const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
140
140
 
141
- /** @type {Immutable<LH.Config.Settings>} */
141
+ /** @type {LH.Audit.Context['settings']} */
142
142
  const metricSettings = {
143
143
  ...context.settings,
144
144
  throttlingMethod: 'simulate',
@@ -194,103 +194,102 @@ class CacheHeaders extends Audit {
194
194
  * @param {LH.Audit.Context} context
195
195
  * @return {Promise<LH.Audit.Product>}
196
196
  */
197
- static audit(artifacts, context) {
197
+ static async audit(artifacts, context) {
198
198
  const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
199
- return NetworkRecords.request(devtoolsLogs, context).then(records => {
200
- const results = [];
201
- let totalWastedBytes = 0;
202
-
203
- for (const record of records) {
204
- if (!CacheHeaders.isCacheableAsset(record)) continue;
205
-
206
- /** @type {Map<string, string>} */
207
- const headers = new Map();
208
- for (const header of record.responseHeaders || []) {
209
- if (headers.has(header.name.toLowerCase())) {
210
- const previousHeaderValue = headers.get(header.name.toLowerCase());
211
- headers.set(header.name.toLowerCase(),
212
- `${previousHeaderValue}, ${header.value}`);
213
- } else {
214
- headers.set(header.name.toLowerCase(), header.value);
215
- }
216
- }
217
-
218
- const cacheControl = parseCacheControl(headers.get('cache-control'));
219
- if (this.shouldSkipRecord(headers, cacheControl)) {
220
- continue;
199
+ const records = await NetworkRecords.request(devtoolsLogs, context);
200
+ const results = [];
201
+ let totalWastedBytes = 0;
202
+
203
+ for (const record of records) {
204
+ if (!CacheHeaders.isCacheableAsset(record)) continue;
205
+
206
+ /** @type {Map<string, string>} */
207
+ const headers = new Map();
208
+ for (const header of record.responseHeaders || []) {
209
+ if (headers.has(header.name.toLowerCase())) {
210
+ const previousHeaderValue = headers.get(header.name.toLowerCase());
211
+ headers.set(header.name.toLowerCase(),
212
+ `${previousHeaderValue}, ${header.value}`);
213
+ } else {
214
+ headers.set(header.name.toLowerCase(), header.value);
221
215
  }
216
+ }
222
217
 
223
- // Ignore if cacheLifetimeInSeconds is a nonpositive number.
224
- let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
225
- headers, cacheControl);
226
- if (cacheLifetimeInSeconds !== null &&
227
- (!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
228
- continue;
229
- }
230
- cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
231
-
232
- // Ignore assets whose cache lifetime is already high enough
233
- const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
234
- if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
235
-
236
- const url = UrlUtils.elideDataURI(record.url);
237
- const totalBytes = record.transferSize || 0;
238
- const wastedBytes = (1 - cacheHitProbability) * totalBytes;
239
-
240
- totalWastedBytes += wastedBytes;
241
-
242
- // Include cacheControl info (if it exists) per url as a diagnostic.
243
- /** @type {LH.Audit.Details.DebugData|undefined} */
244
- let debugData;
245
- if (cacheControl) {
246
- debugData = {
247
- type: 'debugdata',
248
- ...cacheControl,
249
- };
250
- }
218
+ const cacheControl = parseCacheControl(headers.get('cache-control'));
219
+ if (this.shouldSkipRecord(headers, cacheControl)) {
220
+ continue;
221
+ }
251
222
 
252
- results.push({
253
- url,
254
- debugData,
255
- cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
256
- cacheHitProbability,
257
- totalBytes,
258
- wastedBytes,
259
- });
223
+ // Ignore if cacheLifetimeInSeconds is a nonpositive number.
224
+ let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
225
+ headers, cacheControl);
226
+ if (cacheLifetimeInSeconds !== null &&
227
+ (!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
228
+ continue;
229
+ }
230
+ cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
231
+
232
+ // Ignore assets whose cache lifetime is already high enough
233
+ const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
234
+ if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
235
+
236
+ const url = UrlUtils.elideDataURI(record.url);
237
+ const totalBytes = record.transferSize || 0;
238
+ const wastedBytes = (1 - cacheHitProbability) * totalBytes;
239
+
240
+ totalWastedBytes += wastedBytes;
241
+
242
+ // Include cacheControl info (if it exists) per url as a diagnostic.
243
+ /** @type {LH.Audit.Details.DebugData|undefined} */
244
+ let debugData;
245
+ if (cacheControl) {
246
+ debugData = {
247
+ type: 'debugdata',
248
+ ...cacheControl,
249
+ };
260
250
  }
261
251
 
262
- results.sort((a, b) => {
263
- return a.cacheLifetimeMs - b.cacheLifetimeMs ||
264
- b.totalBytes - a.totalBytes ||
265
- a.url.localeCompare(b.url);
252
+ results.push({
253
+ url,
254
+ debugData,
255
+ cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
256
+ cacheHitProbability,
257
+ totalBytes,
258
+ wastedBytes,
266
259
  });
260
+ }
267
261
 
268
- const score = Audit.computeLogNormalScore(
269
- {p10: context.options.p10, median: context.options.median},
270
- totalWastedBytes
271
- );
272
-
273
- /** @type {LH.Audit.Details.Table['headings']} */
274
- const headings = [
275
- {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
276
- // TODO(i18n): pre-compute localized duration
277
- {key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
278
- displayUnit: 'duration'},
279
- {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
280
- displayUnit: 'kb', granularity: 1},
281
- ];
282
-
283
- const summary = {wastedBytes: totalWastedBytes};
284
- const details = Audit.makeTableDetails(headings, results, summary);
285
-
286
- return {
287
- score,
288
- numericValue: totalWastedBytes,
289
- numericUnit: 'byte',
290
- displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
291
- details,
292
- };
262
+ results.sort((a, b) => {
263
+ return a.cacheLifetimeMs - b.cacheLifetimeMs ||
264
+ b.totalBytes - a.totalBytes ||
265
+ a.url.localeCompare(b.url);
293
266
  });
267
+
268
+ const score = Audit.computeLogNormalScore(
269
+ {p10: context.options.p10, median: context.options.median},
270
+ totalWastedBytes
271
+ );
272
+
273
+ /** @type {LH.Audit.Details.Table['headings']} */
274
+ const headings = [
275
+ {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
276
+ // TODO(i18n): pre-compute localized duration
277
+ {key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
278
+ displayUnit: 'duration'},
279
+ {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
280
+ displayUnit: 'kb', granularity: 1},
281
+ ];
282
+
283
+ const summary = {wastedBytes: totalWastedBytes};
284
+ const details = Audit.makeTableDetails(headings, results, summary);
285
+
286
+ return {
287
+ score,
288
+ numericValue: totalWastedBytes,
289
+ numericUnit: 'byte',
290
+ displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
291
+ details,
292
+ };
294
293
  }
295
294
  }
296
295
 
@@ -166,52 +166,51 @@ class CriticalRequestChains extends Audit {
166
166
  * @param {LH.Audit.Context} context
167
167
  * @return {Promise<LH.Audit.Product>}
168
168
  */
169
- static audit(artifacts, context) {
169
+ static async audit(artifacts, context) {
170
170
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
171
171
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
172
172
  const URL = artifacts.URL;
173
- return ComputedChains.request({devtoolsLog, trace, URL}, context).then(chains => {
174
- let chainCount = 0;
175
- /**
176
- * @param {LH.Audit.Details.SimpleCriticalRequestNode} node
177
- * @param {number} depth
178
- */
179
- function walk(node, depth) {
180
- const childIds = Object.keys(node);
181
-
182
- childIds.forEach(id => {
183
- const child = node[id];
184
- if (child.children) {
185
- walk(child.children, depth + 1);
186
- } else {
187
- // if the node doesn't have a children field, then it is a leaf, so +1
188
- chainCount++;
189
- }
190
- }, '');
191
- }
192
- // Convert
193
- const flattenedChains = CriticalRequestChains.flattenRequests(chains);
194
-
195
- // Account for initial navigation
196
- const initialNavKey = Object.keys(flattenedChains)[0];
197
- const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
198
- if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
199
- walk(initialNavChildren, 0);
200
- }
173
+ const chains = await ComputedChains.request({devtoolsLog, trace, URL}, context);
174
+ let chainCount = 0;
175
+ /**
176
+ * @param {LH.Audit.Details.SimpleCriticalRequestNode} node
177
+ * @param {number} depth
178
+ */
179
+ function walk(node, depth) {
180
+ const childIds = Object.keys(node);
201
181
 
202
- const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);
203
-
204
- return {
205
- score: Number(chainCount === 0),
206
- notApplicable: chainCount === 0,
207
- displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
208
- details: {
209
- type: 'criticalrequestchain',
210
- chains: flattenedChains,
211
- longestChain,
212
- },
213
- };
214
- });
182
+ childIds.forEach(id => {
183
+ const child = node[id];
184
+ if (child.children) {
185
+ walk(child.children, depth + 1);
186
+ } else {
187
+ // if the node doesn't have a children field, then it is a leaf, so +1
188
+ chainCount++;
189
+ }
190
+ }, '');
191
+ }
192
+ // Convert
193
+ const flattenedChains = CriticalRequestChains.flattenRequests(chains);
194
+
195
+ // Account for initial navigation
196
+ const initialNavKey = Object.keys(flattenedChains)[0];
197
+ const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
198
+ if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
199
+ walk(initialNavChildren, 0);
200
+ }
201
+
202
+ const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);
203
+
204
+ return {
205
+ score: Number(chainCount === 0),
206
+ notApplicable: chainCount === 0,
207
+ displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
208
+ details: {
209
+ type: 'criticalrequestchain',
210
+ chains: flattenedChains,
211
+ longestChain,
212
+ },
213
+ };
215
214
  }
216
215
  }
217
216
 
@@ -70,53 +70,52 @@ class HTTPS extends Audit {
70
70
  * @param {LH.Audit.Context} context
71
71
  * @return {Promise<LH.Audit.Product>}
72
72
  */
73
- static audit(artifacts, context) {
73
+ static async audit(artifacts, context) {
74
74
  const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
75
- return NetworkRecords.request(devtoolsLogs, context).then(networkRecords => {
76
- const insecureURLs = networkRecords
77
- .filter(record => !NetworkRequest.isSecureRequest(record))
78
- .map(record => UrlUtils.elideDataURI(record.url));
75
+ const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
76
+ const insecureURLs = networkRecords
77
+ .filter(record => !NetworkRequest.isSecureRequest(record))
78
+ .map(record => UrlUtils.elideDataURI(record.url));
79
79
 
80
- /** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
81
- const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));
80
+ /** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
81
+ const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));
82
82
 
83
- /** @type {LH.Audit.Details.Table['headings']} */
84
- const headings = [
85
- {key: 'url', valueType: 'url', label: str_(UIStrings.columnInsecureURL)},
86
- {key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
87
- ];
83
+ /** @type {LH.Audit.Details.Table['headings']} */
84
+ const headings = [
85
+ {key: 'url', valueType: 'url', label: str_(UIStrings.columnInsecureURL)},
86
+ {key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
87
+ ];
88
88
 
89
- for (const details of artifacts.InspectorIssues.mixedContentIssue) {
90
- let item = items.find(item => item.url === details.insecureURL);
91
- if (!item) {
92
- item = {url: details.insecureURL};
93
- items.push(item);
94
- }
95
- item.resolution = resolutionToString[details.resolutionStatus] ?
96
- str_(resolutionToString[details.resolutionStatus]) :
97
- details.resolutionStatus;
89
+ for (const details of artifacts.InspectorIssues.mixedContentIssue) {
90
+ let item = items.find(item => item.url === details.insecureURL);
91
+ if (!item) {
92
+ item = {url: details.insecureURL};
93
+ items.push(item);
98
94
  }
95
+ item.resolution = resolutionToString[details.resolutionStatus] ?
96
+ str_(resolutionToString[details.resolutionStatus]) :
97
+ details.resolutionStatus;
98
+ }
99
99
 
100
- // If a resolution wasn't assigned from an InspectorIssue, then the item
101
- // is not blocked by the browser but we've determined it is insecure anyhow.
102
- // For example, if the URL is localhost, all `http` requests are valid
103
- // (localhost is a secure context), but we still identify `http` requests
104
- // as an "Allowed" insecure URL.
105
- for (const item of items) {
106
- if (!item.resolution) item.resolution = str_(UIStrings.allowed);
107
- }
100
+ // If a resolution wasn't assigned from an InspectorIssue, then the item
101
+ // is not blocked by the browser but we've determined it is insecure anyhow.
102
+ // For example, if the URL is localhost, all `http` requests are valid
103
+ // (localhost is a secure context), but we still identify `http` requests
104
+ // as an "Allowed" insecure URL.
105
+ for (const item of items) {
106
+ if (!item.resolution) item.resolution = str_(UIStrings.allowed);
107
+ }
108
108
 
109
- let displayValue;
110
- if (items.length > 0) {
111
- displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
112
- }
109
+ let displayValue;
110
+ if (items.length > 0) {
111
+ displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
112
+ }
113
113
 
114
- return {
115
- score: Number(items.length === 0),
116
- displayValue,
117
- details: Audit.makeTableDetails(headings, items),
118
- };
119
- });
114
+ return {
115
+ score: Number(items.length === 0),
116
+ displayValue,
117
+ details: Audit.makeTableDetails(headings, items),
118
+ };
120
119
  }
121
120
  }
122
121
 
@@ -49,7 +49,7 @@ class FirstContentfulPaint3G extends Audit {
49
49
  const gatherContext = artifacts.GatherContext;
50
50
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
51
51
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
52
- /** @type {Immutable<LH.Config.Settings>} */
52
+ /** @type {LH.Audit.Context['settings']} */
53
53
  const settings = {...context.settings, throttlingMethod: 'simulate', throttling: regular3G};
54
54
  const metricComputationData = {trace, devtoolsLog, gatherContext, settings, URL: artifacts.URL};
55
55
  const metricResult = await ComputedFcp.request(metricComputationData, context);
@@ -65,7 +65,7 @@ class ResourceBudget extends Audit {
65
65
  }
66
66
 
67
67
  /**
68
- * @param {Immutable<LH.Budget>} budget
68
+ * @param {LH.Util.Immutable<LH.Budget>} budget
69
69
  * @param {Record<LH.Budget.ResourceType, ResourceEntry>} summary
70
70
  * @return {Array<BudgetItem>}
71
71
  */
@@ -90,63 +90,60 @@ class IsCrawlable extends Audit {
90
90
  * @param {LH.Audit.Context} context
91
91
  * @return {Promise<LH.Audit.Product>}
92
92
  */
93
- static audit(artifacts, context) {
93
+ static async audit(artifacts, context) {
94
94
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
95
95
  const metaRobots = artifacts.MetaElements.find(meta => meta.name === 'robots');
96
+ const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
97
+ /** @type {LH.Audit.Details.Table['items']} */
98
+ const blockingDirectives = [];
99
+
100
+ if (metaRobots) {
101
+ const metaRobotsContent = metaRobots.content || '';
102
+ const isBlocking = hasBlockingDirective(metaRobotsContent);
103
+
104
+ if (isBlocking) {
105
+ blockingDirectives.push({
106
+ source: {
107
+ ...Audit.makeNodeItem(metaRobots.node),
108
+ snippet: `<meta name="robots" content="${metaRobotsContent}" />`,
109
+ },
110
+ });
111
+ }
112
+ }
113
+
114
+ mainResource.responseHeaders && mainResource.responseHeaders
115
+ .filter(h => h.name.toLowerCase() === ROBOTS_HEADER && !hasUserAgent(h.value) &&
116
+ hasBlockingDirective(h.value))
117
+ .forEach(h => blockingDirectives.push({source: `${h.name}: ${h.value}`}));
118
+
119
+ if (artifacts.RobotsTxt.content) {
120
+ const robotsFileUrl = new URL('/robots.txt', mainResource.url);
121
+ const robotsTxt = robotsParser(robotsFileUrl.href, artifacts.RobotsTxt.content);
122
+
123
+ if (!robotsTxt.isAllowed(mainResource.url)) {
124
+ const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
125
+ blockingDirectives.push({
126
+ source: {
127
+ type: /** @type {const} */ ('source-location'),
128
+ url: robotsFileUrl.href,
129
+ urlProvider: /** @type {const} */ ('network'),
130
+ line: line - 1,
131
+ column: 0,
132
+ },
133
+ });
134
+ }
135
+ }
136
+
137
+ /** @type {LH.Audit.Details.Table['headings']} */
138
+ const headings = [
139
+ {key: 'source', valueType: 'code', label: 'Blocking Directive Source'},
140
+ ];
141
+ const details = Audit.makeTableDetails(headings, blockingDirectives);
96
142
 
97
- return MainResource.request({devtoolsLog, URL: artifacts.URL}, context)
98
- .then(mainResource => {
99
- /** @type {LH.Audit.Details.Table['items']} */
100
- const blockingDirectives = [];
101
-
102
- if (metaRobots) {
103
- const metaRobotsContent = metaRobots.content || '';
104
- const isBlocking = hasBlockingDirective(metaRobotsContent);
105
-
106
- if (isBlocking) {
107
- blockingDirectives.push({
108
- source: {
109
- ...Audit.makeNodeItem(metaRobots.node),
110
- snippet: `<meta name="robots" content="${metaRobotsContent}" />`,
111
- },
112
- });
113
- }
114
- }
115
-
116
- mainResource.responseHeaders && mainResource.responseHeaders
117
- .filter(h => h.name.toLowerCase() === ROBOTS_HEADER && !hasUserAgent(h.value) &&
118
- hasBlockingDirective(h.value))
119
- .forEach(h => blockingDirectives.push({source: `${h.name}: ${h.value}`}));
120
-
121
- if (artifacts.RobotsTxt.content) {
122
- const robotsFileUrl = new URL('/robots.txt', mainResource.url);
123
- const robotsTxt = robotsParser(robotsFileUrl.href, artifacts.RobotsTxt.content);
124
-
125
- if (!robotsTxt.isAllowed(mainResource.url)) {
126
- const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
127
- blockingDirectives.push({
128
- source: {
129
- type: /** @type {const} */ ('source-location'),
130
- url: robotsFileUrl.href,
131
- urlProvider: /** @type {const} */ ('network'),
132
- line: line - 1,
133
- column: 0,
134
- },
135
- });
136
- }
137
- }
138
-
139
- /** @type {LH.Audit.Details.Table['headings']} */
140
- const headings = [
141
- {key: 'source', valueType: 'code', label: 'Blocking Directive Source'},
142
- ];
143
- const details = Audit.makeTableDetails(headings, blockingDirectives);
144
-
145
- return {
146
- score: Number(blockingDirectives.length === 0),
147
- details,
148
- };
149
- });
143
+ return {
144
+ score: Number(blockingDirectives.length === 0),
145
+ details,
146
+ };
150
147
  }
151
148
  }
152
149
 
@@ -80,7 +80,7 @@ class TimingBudget extends Audit {
80
80
  }
81
81
 
82
82
  /**
83
- * @param {Immutable<LH.Budget>} budget
83
+ * @param {LH.Util.Immutable<LH.Budget>} budget
84
84
  * @param {LH.Artifacts.TimingSummary} summary
85
85
  * @return {Array<BudgetItem>}
86
86
  */
@@ -64,54 +64,53 @@ class UserTimings extends Audit {
64
64
  * @param {LH.Audit.Context} context
65
65
  * @return {Promise<LH.Audit.Product>}
66
66
  */
67
- static audit(artifacts, context) {
67
+ static async audit(artifacts, context) {
68
68
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
69
- return ComputedUserTimings.request(trace, context).then(computedUserTimings => {
70
- const userTimings = computedUserTimings.filter(UserTimings.excludeEvent);
71
- const tableRows = userTimings.map(item => {
72
- return {
73
- name: item.name,
74
- startTime: item.startTime,
75
- duration: item.isMark ? undefined : item.duration,
76
- timingType: item.isMark ? 'Mark' : 'Measure',
77
- };
78
- }).sort((itemA, itemB) => {
79
- if (itemA.timingType === itemB.timingType) {
80
- // If both items are the same type, sort in ascending order by time
81
- return itemA.startTime - itemB.startTime;
82
- } else if (itemA.timingType === 'Measure') {
83
- // Put measures before marks
84
- return -1;
85
- } else {
86
- return 1;
87
- }
88
- });
69
+ const computedUserTimings = await ComputedUserTimings.request(trace, context);
70
+ const userTimings = computedUserTimings.filter(UserTimings.excludeEvent);
71
+ const tableRows = userTimings.map(item => {
72
+ return {
73
+ name: item.name,
74
+ startTime: item.startTime,
75
+ duration: item.isMark ? undefined : item.duration,
76
+ timingType: item.isMark ? 'Mark' : 'Measure',
77
+ };
78
+ }).sort((itemA, itemB) => {
79
+ if (itemA.timingType === itemB.timingType) {
80
+ // If both items are the same type, sort in ascending order by time
81
+ return itemA.startTime - itemB.startTime;
82
+ } else if (itemA.timingType === 'Measure') {
83
+ // Put measures before marks
84
+ return -1;
85
+ } else {
86
+ return 1;
87
+ }
88
+ });
89
89
 
90
- /** @type {LH.Audit.Details.Table['headings']} */
91
- const headings = [
92
- {key: 'name', valueType: 'text', label: str_(i18n.UIStrings.columnName)},
93
- {key: 'timingType', valueType: 'text', label: str_(UIStrings.columnType)},
94
- {key: 'startTime', valueType: 'ms', granularity: 0.01,
95
- label: str_(i18n.UIStrings.columnStartTime)},
96
- {key: 'duration', valueType: 'ms', granularity: 0.01,
97
- label: str_(i18n.UIStrings.columnDuration)},
98
- ];
90
+ /** @type {LH.Audit.Details.Table['headings']} */
91
+ const headings = [
92
+ {key: 'name', valueType: 'text', label: str_(i18n.UIStrings.columnName)},
93
+ {key: 'timingType', valueType: 'text', label: str_(UIStrings.columnType)},
94
+ {key: 'startTime', valueType: 'ms', granularity: 0.01,
95
+ label: str_(i18n.UIStrings.columnStartTime)},
96
+ {key: 'duration', valueType: 'ms', granularity: 0.01,
97
+ label: str_(i18n.UIStrings.columnDuration)},
98
+ ];
99
99
 
100
- const details = Audit.makeTableDetails(headings, tableRows);
100
+ const details = Audit.makeTableDetails(headings, tableRows);
101
101
 
102
- let displayValue;
103
- if (userTimings.length) {
104
- displayValue = str_(UIStrings.displayValue, {itemCount: userTimings.length});
105
- }
102
+ let displayValue;
103
+ if (userTimings.length) {
104
+ displayValue = str_(UIStrings.displayValue, {itemCount: userTimings.length});
105
+ }
106
106
 
107
- return {
108
- // mark the audit as notApplicable if there were no user timings
109
- score: Number(userTimings.length === 0),
110
- notApplicable: userTimings.length === 0,
111
- displayValue,
112
- details,
113
- };
114
- });
107
+ return {
108
+ // mark the audit as notApplicable if there were no user timings
109
+ score: Number(userTimings.length === 0),
110
+ notApplicable: userTimings.length === 0,
111
+ displayValue,
112
+ details,
113
+ };
115
114
  }
116
115
  }
117
116
 
@@ -12,9 +12,9 @@ import {ArbitraryEqualityMap} from '../lib/arbitrary-equality-map.js';
12
12
  * Decorate computableArtifact with a caching `request()` method which will
13
13
  * automatically call `computableArtifact.compute_()` under the hood.
14
14
  * @template {{name: string, compute_(dependencies: unknown, context: LH.Artifacts.ComputedContext): Promise<unknown>}} C
15
- * @template {Array<keyof FirstParamType<C['compute_']>>} K
15
+ * @template {Array<keyof LH.Util.FirstParamType<C['compute_']>>} K
16
16
  * @param {C} computableArtifact
17
- * @param {(K & ([keyof FirstParamType<C['compute_']>] extends [K[number]] ? unknown : never)) | null} keys List of properties of `dependencies` used by `compute_`; other properties are filtered out. Use `null` to allow all properties. Ensures that only required properties are used for caching result.
17
+ * @param {(K & ([keyof LH.Util.FirstParamType<C['compute_']>] extends [K[number]] ? unknown : never)) | null} keys List of properties of `dependencies` used by `compute_`; other properties are filtered out. Use `null` to allow all properties. Ensures that only required properties are used for caching result.
18
18
  */
19
19
  function makeComputedArtifact(computableArtifact, keys) {
20
20
  // tsc (3.1) has more difficulty with template inter-references in jsdoc, so
@@ -22,7 +22,7 @@ function makeComputedArtifact(computableArtifact, keys) {
22
22
  // polymorphic-this behavior for C.
23
23
  /**
24
24
  * Return an automatically cached result from the computed artifact.
25
- * @param {FirstParamType<C['compute_']>} dependencies
25
+ * @param {LH.Util.FirstParamType<C['compute_']>} dependencies
26
26
  * @param {LH.Artifacts.ComputedContext} context
27
27
  * @return {ReturnType<C['compute_']>}
28
28
  */
@@ -11,7 +11,7 @@ import {NetworkAnalysis} from './network-analysis.js';
11
11
 
12
12
  class LoadSimulator {
13
13
  /**
14
- * @param {{devtoolsLog: LH.DevtoolsLog, settings: Immutable<LH.Config.Settings>}} data
14
+ * @param {{devtoolsLog: LH.DevtoolsLog, settings: LH.Audit.Context['settings']}} data
15
15
  * @param {LH.Artifacts.ComputedContext} context
16
16
  * @return {Promise<Simulator>}
17
17
  */
@@ -131,7 +131,7 @@ class Responsiveness {
131
131
  }
132
132
 
133
133
  /**
134
- * @param {{trace: LH.Trace, settings: Immutable<LH.Config.Settings>}} data
134
+ * @param {{trace: LH.Trace, settings: LH.Audit.Context['settings']}} data
135
135
  * @param {LH.Artifacts.ComputedContext} context
136
136
  * @return {Promise<EventTimingEvent|FallbackTimingEvent|null>}
137
137
  */
@@ -24,7 +24,7 @@ class TimingSummary {
24
24
  * @param {LH.Trace} trace
25
25
  * @param {LH.DevtoolsLog} devtoolsLog
26
26
  * @param {LH.Artifacts['GatherContext']} gatherContext
27
- * @param {ImmutableObject<LH.Config.Settings>} settings
27
+ * @param {LH.Util.ImmutableObject<LH.Config.Settings>} settings
28
28
  * @param {LH.Artifacts['URL']} URL
29
29
  * @param {LH.Artifacts.ComputedContext} context
30
30
  * @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
@@ -134,7 +134,7 @@ class TimingSummary {
134
134
  return {metrics, debugInfo};
135
135
  }
136
136
  /**
137
- * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, gatherContext: LH.Artifacts['GatherContext']; settings: ImmutableObject<LH.Config.Settings>, URL: LH.Artifacts['URL']}} data
137
+ * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, gatherContext: LH.Artifacts['GatherContext']; settings: LH.Util.ImmutableObject<LH.Config.Settings>, URL: LH.Artifacts['URL']}} data
138
138
  * @param {LH.Artifacts.ComputedContext} context
139
139
  * @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
140
140
  */
@@ -11,7 +11,7 @@ import {NetworkRecords} from './network-records.js';
11
11
  class NetworkAnalysis {
12
12
  /**
13
13
  * @param {Array<LH.Artifacts.NetworkRequest>} records
14
- * @return {StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
14
+ * @return {LH.Util.StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
15
15
  */
16
16
  static computeRTTAndServerResponseTime(records) {
17
17
  // First pass compute the estimated observed RTT to each origin's servers.
@@ -34,7 +34,7 @@ class ResourceSummary {
34
34
  /**
35
35
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
36
36
  * @param {LH.Artifacts.URL} URLArtifact
37
- * @param {ImmutableObject<LH.Budget[]|null>} budgets
37
+ * @param {LH.Util.ImmutableObject<LH.Budget[]|null>} budgets
38
38
  * @return {Record<LH.Budget.ResourceType, ResourceEntry>}
39
39
  */
40
40
  static summarize(networkRecords, URLArtifact, budgets) {
@@ -99,7 +99,7 @@ class ResourceSummary {
99
99
  }
100
100
 
101
101
  /**
102
- * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: ImmutableObject<LH.Budget[]|null>}} data
102
+ * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: LH.Util.ImmutableObject<LH.Budget[]|null>}} data
103
103
  * @param {LH.Artifacts.ComputedContext} context
104
104
  * @return {Promise<Record<LH.Budget.ResourceType,ResourceEntry>>}
105
105
  */
@@ -135,9 +135,9 @@ class Budget {
135
135
  * Returns the budget that applies to a given URL.
136
136
  * If multiple budgets match based on thier 'path' property,
137
137
  * then the last-listed of those budgets is returned.
138
- * @param {Immutable<Array<LH.Budget>>|null} budgets
138
+ * @param {LH.Util.Immutable<Array<LH.Budget>>|null} budgets
139
139
  * @param {string|undefined} url
140
- * @return {Immutable<LH.Budget> | undefined} budget
140
+ * @return {LH.Util.Immutable<LH.Budget> | undefined} budget
141
141
  */
142
142
  static getMatchingBudget(budgets, url) {
143
143
  if (budgets === null || url === undefined) return;
@@ -299,10 +299,10 @@ function requireAudit(auditPath, coreAuditList, configDir) {
299
299
  * Creates a settings object from potential flags object by dropping all the properties
300
300
  * that don't exist on Config.Settings.
301
301
  * @param {Partial<LH.Flags>=} flags
302
- * @return {RecursivePartial<LH.Config.Settings>}
302
+ * @return {LH.Util.RecursivePartial<LH.Config.Settings>}
303
303
  */
304
304
  function cleanFlagsForSettings(flags = {}) {
305
- /** @type {RecursivePartial<LH.Config.Settings>} */
305
+ /** @type {LH.Util.RecursivePartial<LH.Config.Settings>} */
306
306
  const settings = {};
307
307
 
308
308
  for (const key of Object.keys(flags)) {
@@ -25,7 +25,9 @@ const MAX_WEBP_SIZE = 16383;
25
25
  * @param {S} str
26
26
  */
27
27
  function kebabCaseToCamelCase(str) {
28
- return /** @type {KebabToCamelCase<S>} */ (str.replace(/(-\w)/g, m => m[1].toUpperCase()));
28
+ return /** @type {LH.Util.KebabToCamelCase<S>} */ (
29
+ str.replace(/(-\w)/g, m => m[1].toUpperCase())
30
+ );
29
31
  }
30
32
 
31
33
  /* c8 ignore start */
@@ -130,7 +130,7 @@ function convertNodeTimingsToTrace(nodeTimings) {
130
130
  if (startTime === endTime) endTime += 0.3;
131
131
 
132
132
  const requestData = {requestId: requestId.toString(), frame};
133
- /** @type {StrictOmit<LH.TraceEvent, 'name'|'ts'|'args'>} */
133
+ /** @type {LH.Util.StrictOmit<LH.TraceEvent, 'name'|'ts'|'args'>} */
134
134
  const baseRequestEvent = {...baseEvent, ph: 'I', s: 't', dur: 0};
135
135
 
136
136
  const sendRequestData = {
@@ -89,7 +89,7 @@ const HEADER_PROTOCOL_IS_H2 = 'X-ProtocolIsH2';
89
89
  * @property {number} responseMs
90
90
  */
91
91
 
92
- /** @type {SelfMap<LH.Crdp.Network.ResourceType>} */
92
+ /** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */
93
93
  const RESOURCE_TYPES = {
94
94
  XHR: 'XHR',
95
95
  Fetch: 'Fetch',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "9.5.0-dev.20221115",
4
+ "version": "9.5.0-dev.20221117",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -18,10 +18,12 @@ import Gatherer from './gatherer';
18
18
  import {IcuMessage} from './lhr/i18n';
19
19
  import LHResult from './lhr/lhr'
20
20
  import Protocol from './protocol';
21
+ import Util from './utility-types.js';
22
+ import Audit from './audit.js';
21
23
 
22
24
  export interface Artifacts extends BaseArtifacts, GathererArtifacts {}
23
25
 
24
- export type FRArtifacts = StrictOmit<Artifacts,
26
+ export type FRArtifacts = Util.StrictOmit<Artifacts,
25
27
  | 'Fonts'
26
28
  | 'Manifest'
27
29
  | 'MixedContent'
@@ -179,7 +181,7 @@ export interface GathererArtifacts extends PublicGathererArtifacts,LegacyBaseArt
179
181
  }
180
182
 
181
183
  declare module Artifacts {
182
- type ComputedContext = Immutable<{
184
+ type ComputedContext = Util.Immutable<{
183
185
  computedCache: Map<string, ArbitraryEqualityMap>;
184
186
  }>;
185
187
 
@@ -634,7 +636,7 @@ declare module Artifacts {
634
636
  interface MetricComputationDataInput {
635
637
  devtoolsLog: DevtoolsLog;
636
638
  trace: Trace;
637
- settings: Immutable<Config.Settings>;
639
+ settings: Audit.Context['settings'];
638
640
  gatherContext: Artifacts['GatherContext'];
639
641
  simulator?: InstanceType<typeof Simulator>;
640
642
  URL: Artifacts['URL'];
package/types/audit.d.ts CHANGED
@@ -11,6 +11,7 @@ import Config from './config';
11
11
  import Gatherer from './gatherer';
12
12
  import {IcuMessage} from './lhr/i18n';
13
13
  import * as AuditResult from './lhr/audit-result';
14
+ import Util from './utility-types.js';
14
15
 
15
16
  declare module Audit {
16
17
  export import Details = AuditDetails;
@@ -18,7 +19,7 @@ declare module Audit {
18
19
  export type ScoreDisplayMode = AuditResult.ScoreDisplayMode;
19
20
  export type ScoreDisplayModes = AuditResult.ScoreDisplayModes;
20
21
 
21
- type Context = Immutable<{
22
+ type Context = Util.Immutable<{
22
23
  /** audit options */
23
24
  options: Record<string, any>;
24
25
  settings: Config.Settings;
@@ -8,101 +8,6 @@ import {Artifacts} from './artifacts';
8
8
  import LHResult from './lhr/lhr';
9
9
  import {SharedFlagsSettings, OutputMode} from './lhr/settings';
10
10
 
11
- declare global {
12
- // Augment Intl to include
13
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
14
- namespace Intl {
15
- var getCanonicalLocales: (locales?: string | Array<string>) => Array<string>;
16
- }
17
-
18
- interface Window {
19
- // Cached native functions/objects for use in case the page overwrites them.
20
- // See: `executionContext.cacheNativesOnNewDocument`.
21
- __nativePromise: PromiseConstructor;
22
- __nativePerformance: Performance;
23
- __nativeFetch: typeof fetch,
24
- __nativeURL: typeof URL;
25
- __ElementMatches: Element['matches'];
26
- __HTMLElementBoundingClientRect: HTMLElement['getBoundingClientRect'];
27
-
28
- /** Used for monitoring long tasks in the test page. */
29
- ____lastLongTask?: number;
30
-
31
- /** Used by FullPageScreenshot gatherer. */
32
- __lighthouseNodesDontTouchOrAllVarianceGoesAway: Map<Element, string>;
33
- __lighthouseExecutionContextUniqueIdentifier?: number;
34
-
35
- /** Injected into the page when the `--debug` flag is used. */
36
- continueLighthouseRun(): void;
37
-
38
- // Not defined in tsc yet: https://github.com/microsoft/TypeScript/issues/40807
39
- requestIdleCallback(callback: (deadline: {didTimeout: boolean, timeRemaining: () => DOMHighResTimeStamp}) => void, options?: {timeout: number}): number;
40
- }
41
-
42
- /** Make properties K in T optional. */
43
- type MakeOptional<T, K extends keyof T> = {
44
- [P in Exclude<keyof T, K>]: T[P]
45
- } & {
46
- [P in K]+?: T[P]
47
- }
48
-
49
- /** An object with the keys in the union K mapped to themselves as values. */
50
- type SelfMap<K extends string> = {
51
- [P in K]: P;
52
- };
53
-
54
- /** Make optional all properties on T and any properties on object properties of T. */
55
- type RecursivePartial<T> =
56
- // Recurse into arrays and tuples: elements aren't (newly) optional, but any properties they have are.
57
- T extends (infer U)[] ? RecursivePartial<U>[] :
58
- // Recurse into objects: properties and any of their properties are optional.
59
- T extends object ? {[P in keyof T]?: RecursivePartial<T[P]>} :
60
- // Strings, numbers, etc. (terminal types) end here.
61
- T;
62
-
63
- /** Recursively makes all properties of T read-only. */
64
- type Immutable<T> =
65
- T extends Function ? T :
66
- T extends Array<infer R> ? ImmutableArray<R> :
67
- T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
68
- T extends Set<infer M> ? ImmutableSet<M> :
69
- T extends object ? ImmutableObject<T> :
70
- T
71
-
72
- // Intermediate immutable types. Prefer e.g. Immutable<Set<T>> over direct use.
73
- type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
74
- type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
75
- type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
76
- type ImmutableObject<T> = {
77
- readonly [K in keyof T]: Immutable<T[K]>;
78
- };
79
-
80
- /**
81
- * Exclude void from T
82
- */
83
- type NonVoid<T> = T extends void ? never : T;
84
-
85
- /** Remove properties K from T. */
86
- type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
87
-
88
- /** Obtain the type of the first parameter of a function. */
89
- type FirstParamType<T extends (arg1: any, ...args: any[]) => any> =
90
- T extends (arg1: infer P, ...args: any[]) => any ? P : never;
91
-
92
- /**
93
- * If `S` is a kebab-style string `S`, convert to camelCase.
94
- */
95
- type KebabToCamelCase<S> =
96
- S extends `${infer T}-${infer U}` ?
97
- `${T}${Capitalize<KebabToCamelCase<U>>}` :
98
- S
99
-
100
- /** Returns T with any kebab-style property names rewritten as camelCase. */
101
- type CamelCasify<T> = {
102
- [K in keyof T as KebabToCamelCase<K>]: T[K];
103
- }
104
- }
105
-
106
11
  /**
107
12
  * Extends the flags in SharedFlagsSettings with flags used to configure the
108
13
  * Lighthouse module but will not end up in the Config settings.
@@ -22,6 +22,7 @@ import * as Settings from './lhr/settings';
22
22
  import Treemap_ from './lhr/treemap';
23
23
  import UserFlow_ from './user-flow';
24
24
  import Puppeteer_ from './puppeteer';
25
+ import Util_ from './utility-types.js';
25
26
 
26
27
  // Construct hierarchy of global types under the LH namespace.
27
28
  declare global {
@@ -29,6 +30,8 @@ declare global {
29
30
  export type ArbitraryEqualityMap = ArbitraryEqualityMap_;
30
31
  export type NavigationRequestor = string | (() => Promise<void> | void);
31
32
 
33
+ export import Util = Util_;
34
+
32
35
  export import Puppeteer = Puppeteer_;
33
36
 
34
37
  // artifacts.d.ts
package/types/node.d.ts CHANGED
@@ -7,6 +7,38 @@
7
7
  declare global {
8
8
  var isDevtools: boolean | undefined;
9
9
  var isLightrider: boolean | undefined;
10
+
11
+ // Augment Intl to include
12
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
13
+ namespace Intl {
14
+ var getCanonicalLocales: (locales?: string | Array<string>) => Array<string>;
15
+ }
16
+
17
+ // Some functions defined in node are stringified and run in the browser.
18
+ // Ensure those functions are working with the correct browser environment.
19
+ interface Window {
20
+ // Cached native functions/objects for use in case the page overwrites them.
21
+ // See: `executionContext.cacheNativesOnNewDocument`.
22
+ __nativePromise: PromiseConstructor;
23
+ __nativePerformance: Performance;
24
+ __nativeFetch: typeof fetch,
25
+ __nativeURL: typeof URL;
26
+ __ElementMatches: Element['matches'];
27
+ __HTMLElementBoundingClientRect: HTMLElement['getBoundingClientRect'];
28
+
29
+ /** Used for monitoring long tasks in the test page. */
30
+ ____lastLongTask?: number;
31
+
32
+ /** Used by FullPageScreenshot gatherer. */
33
+ __lighthouseNodesDontTouchOrAllVarianceGoesAway: Map<Element, string>;
34
+ __lighthouseExecutionContextUniqueIdentifier?: number;
35
+
36
+ /** Injected into the page when the `--debug` flag is used. */
37
+ continueLighthouseRun(): void;
38
+
39
+ // Not defined in tsc yet: https://github.com/microsoft/TypeScript/issues/40807
40
+ requestIdleCallback(callback: (deadline: {didTimeout: boolean, timeRemaining: () => DOMHighResTimeStamp}) => void, options?: {timeout: number}): number;
41
+ }
10
42
  }
11
43
 
12
44
  export {};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ declare module Util {
8
+ /** Make properties K in T optional. */
9
+ type MakeOptional<T, K extends keyof T> = {
10
+ [P in Exclude<keyof T, K>]: T[P]
11
+ } & {
12
+ [P in K]+?: T[P]
13
+ }
14
+
15
+ /** An object with the keys in the union K mapped to themselves as values. */
16
+ type SelfMap<K extends string> = {
17
+ [P in K]: P;
18
+ };
19
+
20
+ /** Make optional all properties on T and any properties on object properties of T. */
21
+ type RecursivePartial<T> =
22
+ // Recurse into arrays and tuples: elements aren't (newly) optional, but any properties they have are.
23
+ T extends (infer U)[] ? RecursivePartial<U>[] :
24
+ // Recurse into objects: properties and any of their properties are optional.
25
+ T extends object ? {[P in keyof T]?: RecursivePartial<T[P]>} :
26
+ // Strings, numbers, etc. (terminal types) end here.
27
+ T;
28
+
29
+ /** Recursively makes all properties of T read-only. */
30
+ type Immutable<T> =
31
+ T extends Function ? T :
32
+ T extends Array<infer R> ? ImmutableArray<R> :
33
+ T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
34
+ T extends Set<infer M> ? ImmutableSet<M> :
35
+ T extends object ? ImmutableObject<T> :
36
+ T
37
+
38
+ // Intermediate immutable types. Prefer e.g. Immutable<Set<T>> over direct use.
39
+ type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
40
+ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
41
+ type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
42
+ type ImmutableObject<T> = {
43
+ readonly [K in keyof T]: Immutable<T[K]>;
44
+ };
45
+
46
+ /**
47
+ * Exclude void from T
48
+ */
49
+ type NonVoid<T> = T extends void ? never : T;
50
+
51
+ /** Remove properties K from T. */
52
+ type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
53
+
54
+ /** Obtain the type of the first parameter of a function. */
55
+ type FirstParamType<T extends (arg1: any, ...args: any[]) => any> =
56
+ T extends (arg1: infer P, ...args: any[]) => any ? P : never;
57
+
58
+ /**
59
+ * If `S` is a kebab-style string `S`, convert to camelCase.
60
+ */
61
+ type KebabToCamelCase<S> =
62
+ S extends `${infer T}-${infer U}` ?
63
+ `${T}${Capitalize<KebabToCamelCase<U>>}` :
64
+ S
65
+
66
+ /** Returns T with any kebab-style property names rewritten as camelCase. */
67
+ type CamelCasify<T> = {
68
+ [K in keyof T as KebabToCamelCase<K>]: T[K];
69
+ }
70
+ }
71
+
72
+ export default Util;