1// Copyright 2019 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
5import fs from 'fs';
6import path from 'path';
7import {parse, print} from 'recast';
8import {promisify} from 'util';
9
10const readFile = promisify(fs.readFile);
11const stat = promisify(fs.stat);
12
13const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end');
14
15// eslint-disable-next-line @typescript-eslint/no-explicit-any
16export async function getMappings(namespace: string, mappings: Map<string, any>, useExternalRefs = false) {
17  const src = namespace.toLocaleLowerCase();
18  const externalModule = path.join(FRONT_END_FOLDER, src, `${src}.js`);
19  const legacy = path.join(FRONT_END_FOLDER, src, `${src}-legacy.js`);
20
21  if (!(await stat(legacy))) {
22    console.error(`Unable to find legacy file: ${legacy}`);
23    process.exit(1);
24  }
25
26  const legacyFileContents = await readFile(legacy, { encoding: 'utf-8' });
27  const ast = parse(legacyFileContents);
28
29  for (const statement of ast.program.body) {
30    if (statement.type !== 'ExpressionStatement') {
31      continue;
32    }
33
34    // We need to check that we have an assignment expression, of which the left and right are both member expressions.
35    // This allows us to extract things like Foo.Bar = FooModule.Bar.Bar from the legacy file, while ignoring self.Foo
36    // and Foo = Foo || {} statements.
37    const isMemberExpressionOnLeftAndRight = statement.expression && statement.expression.left &&
38        statement.expression.right && statement.expression.type === 'AssignmentExpression' &&
39        statement.expression.left.type === 'MemberExpression' && statement.expression.right.type === 'MemberExpression';
40
41    if (isMemberExpressionOnLeftAndRight) {
42      // Rename FooModule back to Foo because we know we will want to use the latter when doing replacements.
43      if (statement.expression.right.object && statement.expression.right.object.object &&
44          statement.expression.right.object.object.type === 'Identifier') {
45        statement.expression.right.object.object.name = statement.expression.right.object.object.name.replace(/Module$/, '');
46      }
47
48      const leftSide = print(statement.expression.left).code;
49      const rightSide = print(statement.expression.right).code;
50      const rightSideParts = rightSide.split('.');
51      const file = useExternalRefs ? externalModule : path.join(FRONT_END_FOLDER, src, rightSideParts[1] + '.js');
52
53      if (rightSideParts[0] === 'Protocol') {
54        rightSideParts[0] = 'ProtocolClient';
55      }
56
57      mappings.set(leftSide, {
58        file,
59        replacement: rightSideParts.join('.'),
60        sameFolderReplacement: rightSideParts[rightSideParts.length - 1],
61      });
62    }
63  }
64
65  return mappings;
66}
67