blob: 1fcd19910e4869e53e104bbf3ee81ffcde053a04 [file] [log] [blame]
Brandon Goddard33104372020-08-13 08:49:23 -07001// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4'use strict';
5
6const fs = require('fs');
7const path = require('path');
8
9const SRC_PATH = path.resolve(__dirname, '..');
10const NODE_MODULES_PATH = path.resolve(SRC_PATH, 'node_modules');
11const espree = require(path.resolve(NODE_MODULES_PATH, '@typescript-eslint', 'parser'));
12
13const USER_METRICS_ENUM_ENDPOINT = '__lastValidEnumPosition';
14
15/**
16 * Determines if a node is a class declaration.
17 * If className is provided, node must also match class name.
18 */
19function isClassNameDeclaration(node, className) {
20 const isClassDeclaration = node.type === 'ExportNamedDeclaration' && node.declaration.type === 'ClassDeclaration';
21 return className ? (isClassDeclaration && node.declaration.id.name === className) : isClassDeclaration;
22}
23
24/**
25 * Finds a function declaration node inside a class declaration node
26 */
27function findFunctionInClass(classNode, functionName) {
28 for (const node of classNode.declaration.body.body) {
29 if (node.key.name === functionName) {
30 return node;
31 }
32 }
33 return null;
34}
35
36/**
37 * Determines if AST Node is a call to register a DevtoolsExperiment
38 */
39function isExperimentRegistrationCall(node) {
40 return node.expression && node.expression.type === 'CallExpression' &&
41 node.expression.callee.property.name === 'register';
42}
43
44/**
45 * Gets list of experiments registered in MainImpl.js.
46 */
47function getMainImplExperimentList(mainImplFile) {
48 const mainAST = espree.parse(mainImplFile, {ecmaVersion: 11, sourceType: 'module', range: true});
49
50 // Find MainImpl Class node
51 let mainImplClassNode;
52 for (const node of mainAST.body) {
53 if (isClassNameDeclaration(node, 'MainImpl')) {
54 mainImplClassNode = node;
55 break;
56 }
57 }
58 if (!mainImplClassNode) {
59 return null;
60 }
61
62 // Find function in MainImpl Class
63 const initializeExperimentNode = findFunctionInClass(mainImplClassNode, '_initializeExperiments');
64 if (!initializeExperimentNode) {
65 return null;
66 }
67
68 // Get list of experiments
69 const experiments = [];
70 for (const statement of initializeExperimentNode.value.body.body) {
71 if (isExperimentRegistrationCall(statement)) {
72 // Experiment name is first argument of registration call
73 experiments.push(statement.expression.arguments[0].value);
74 }
75 }
76 return experiments.length ? experiments : null;
77}
78
79/**
80 * Determines if AST Node is the DevtoolsExperiments Enum declaration
81 */
82function isExperimentEnumDeclaration(node) {
83 return node.type === 'ExportNamedDeclaration' && node.declaration.declarations &&
84 node.declaration.declarations[0].id.name === 'DevtoolsExperiments';
85}
86
87/**
Jan Scheffler9aca8c72021-02-25 10:03:24 +010088 * Gets list of experiments registered in UserMetrics.ts
Brandon Goddard33104372020-08-13 08:49:23 -070089 */
90function getUserMetricExperimentList(userMetricsFile) {
91 const userMetricsAST = espree.parse(userMetricsFile, {ecmaVersion: 11, sourceType: 'module', range: true});
92 for (const node of userMetricsAST.body) {
93 if (isExperimentEnumDeclaration(node)) {
94 return node.declaration.declarations[0].init.properties.map(property => {
95 return property.key.value;
96 });
97 }
98 }
99 return null;
100}
101
102/**
103 * Compares list of experiments, fires error if an experiment is registered without telemetry entry.
104 */
105function compareExperimentLists(mainImplList, userMetricsList) {
106 // Ensure both lists are valid
107 let errorFound = false;
108 if (!mainImplList) {
109 console.log(
110 'Changes to Devtools Experiment registration have prevented this check from finding registered experiments.');
111 console.log('Please update scripts/check_experiments.js to account for the new experiment registration.');
112 errorFound = true;
113 }
114 if (!userMetricsList) {
115 console.log(
116 'Changes to Devtools Experiment UserMetrics enum have prevented this check from finding experiments registered for telemetry.');
117 console.log('Please update scripts/check_experiments.js to account for the new experiment telemetry format.');
118 errorFound = true;
119 }
120 if (errorFound) {
121 process.exit(1);
122 }
123
124 // Ensure both lists match
125 const missingTelemetry = mainImplList.filter(experiment => !userMetricsList.includes(experiment));
126 const staleTelemetry = userMetricsList.filter(
127 experiment => !mainImplList.includes(experiment) && experiment !== USER_METRICS_ENUM_ENDPOINT);
128 if (missingTelemetry.length) {
129 console.log('Devtools Experiments have been added without corresponding histogram update!');
130 console.log(missingTelemetry.join('\n'));
131 console.log(
Jan Scheffler9aca8c72021-02-25 10:03:24 +0100132 'Please ensure that the DevtoolsExperiments enum in UserMetrics.ts is updated with the new experiment.');
Brandon Goddard33104372020-08-13 08:49:23 -0700133 console.log(
134 'Please ensure that a corresponding CL is openend against chromium.src/tools/metrics/histograms/enums.xml to update the DevtoolsExperiments enum');
135 errorFound = true;
136 }
137 if (staleTelemetry.length) {
138 console.log('Devtools Experiments that are no longer registered are still listed in the telemetry enum!');
139 console.log(staleTelemetry.join('\n'));
140 console.log(
Jan Scheffler9aca8c72021-02-25 10:03:24 +0100141 'Please ensure that the DevtoolsExperiments enum in UserMetrics.ts is updated to remove these stale experiments.');
Brandon Goddard33104372020-08-13 08:49:23 -0700142 errorFound = true;
143 }
144 if (errorFound) {
145 process.exit(1);
146 }
147 console.log('DevTools Experiment Telemetry checker passed.');
148}
149
150function main() {
Jan Schefflerb4eb22d2021-04-05 22:38:36 +0200151 const mainImplPath = path.resolve(__dirname, '..', 'front_end', 'main', 'MainImpl.ts');
Brandon Goddard33104372020-08-13 08:49:23 -0700152 const mainImplFile = fs.readFileSync(mainImplPath, 'utf-8');
153
Tim van der Lippee0247312021-04-01 15:25:30 +0100154 const userMetricsPath = path.resolve(__dirname, '..', 'front_end', 'core', 'host', 'UserMetrics.ts');
Brandon Goddard33104372020-08-13 08:49:23 -0700155 const userMetricsFile = fs.readFileSync(userMetricsPath, 'utf-8');
156
157 compareExperimentLists(getMainImplExperimentList(mainImplFile), getUserMetricExperimentList(userMetricsFile));
158}
159
160main();