-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
Copy pathproto-preprocessor.js
110 lines (95 loc) · 3.19 KB
/
proto-preprocessor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'fs';
import esMain from 'es-main';
/**
* @fileoverview Helper functions to transform an LHR into a proto-ready LHR.
*
* FIXME: This file is 100% technical debt. Our eventual goal is for the
* roundtrip JSON to match the Golden LHR 1:1.
*/
/**
* Transform an LHR into a proto-friendly, mostly-compatible LHR.
* @param {LH.Result} lhr
* @return {LH.Result}
*/
function processForProto(lhr) {
/** @type {LH.Result} */
const reportJson = JSON.parse(JSON.stringify(lhr));
// Remove runtimeError if it is NO_ERROR
if (reportJson.runtimeError && reportJson.runtimeError.code === 'NO_ERROR') {
delete reportJson.runtimeError;
}
// Clean up actions that require 'audits' to exist
if (reportJson.audits) {
Object.keys(reportJson.audits).forEach(auditName => {
const audit = reportJson.audits[auditName];
// Rewrite 'not-applicable' and 'not_applicable' scoreDisplayMode to 'notApplicable'. #6201, #6783.
if (audit.scoreDisplayMode) {
// @ts-expect-error ts properly flags this as invalid as it should not happen,
// but remains in preprocessor to protect from proto translation errors from
// old LHRs.
// eslint-disable-next-line max-len
if (audit.scoreDisplayMode === 'not-applicable' || audit.scoreDisplayMode === 'not_applicable') {
audit.scoreDisplayMode = 'notApplicable';
}
}
// Normalize displayValue to always be a string, not an array. #6200
if (Array.isArray(audit.displayValue)) {
/** @type {Array<any>}*/
const values = [];
audit.displayValue.forEach(item => {
values.push(item);
});
audit.displayValue = values.join(' | ');
}
});
}
/**
* Remove any found empty strings, as they are dropped after round-tripping anyway
* @param {any} obj
*/
function removeStrings(obj) {
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'string' && obj[key] === '') {
delete obj[key];
} else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) {
removeStrings(obj[key]);
}
});
} else if (Array.isArray(obj)) {
obj.forEach(item => {
if (typeof item === 'object' || Array.isArray(item)) {
removeStrings(item);
}
});
}
}
removeStrings(reportJson);
return reportJson;
}
// Test if called from the CLI or as a module.
if (esMain(import.meta)) {
// read in the argv for the input & output
const args = process.argv.slice(2);
let input;
let output;
if (args.length) {
// find can return undefined, so default it to '' with OR
input = (args.find(flag => flag.startsWith('--in')) || '').replace('--in=', '');
output = (args.find(flag => flag.startsWith('--out')) || '').replace('--out=', '');
}
if (input && output) {
// process the file
const report = processForProto(JSON.parse(fs.readFileSync(input, 'utf-8')));
// write to output from argv
fs.writeFileSync(output, JSON.stringify(report), 'utf-8');
}
}
export {
processForProto,
};