File: rewrite_imports.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (140 lines) | stat: -rw-r--r-- 6,369 bytes parent folder | download | duplicates (11)
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import os
import re
import sys
import json

from pathlib import Path

_HERE_DIR = Path(__file__).parent
_SOURCE_MAP_CREATOR = (_HERE_DIR / 'rewrite_imports.mjs').resolve()

_NODE_PATH = (_HERE_DIR.parent.parent / 'third_party' / 'node').resolve()
sys.path.append(str(_NODE_PATH))
import node

_CWD = os.getcwd()

# TODO(crbug.com/1320176): Consider either integrating this functionality into
# ts_library.py or replacing the regex if only "tslib" is ever rewritten.
def main(argv):
    parser = argparse.ArgumentParser()
    # List of imports and what they should be rewritten to. Specified as an
    # array of "src|dest" strings. Note that if the src is not a directory, it
    # will be treated as a regex matched against the entire import path. I.e.
    # "foo|bar" will translate to re.sub("^foo$", "bar", line). Since re.sub is
    # used, regex features like referencing groups from `src` in `dest` are
    # available.
    parser.add_argument('--import_mappings', nargs='*')
    # List of rules for renaming imported variables. The format is
    # "path:oldName|newName". When "path" is part of the original path, the
    # variable is renamed to "newName". E.g.
    # Rule: 'lit/static-html:html|staticHtml'
    # Original: import { static } from 'lit/static-html'
    # Rewrite: import { staticHtml } from 'lit/static-html'
    parser.add_argument('--import_var_mappings', nargs='*')
    # The directory to output the rewritten files to.
    parser.add_argument('--out_dir', required=True)
    # The directory to output the manifest file to. The manifest can be used
    # by downstream tools (such as generate_grd) to capture all files that were
    # rewritten.
    parser.add_argument('--manifest_out', required=True)
    # The directory all in_files are under, used to construct a real path on
    # disk via base_dir + in_file.
    parser.add_argument('--base_dir', required=True)
    # List of files to rewrite imports for, all files should be provided
    # relative to base_dir. Each files path is preserved when outputed
    # into out_dir. I.e. "a/b/c/foo" will be outputted to "base_dir/a/b/c/foo".
    parser.add_argument('--in_files', nargs='*')
    args = parser.parse_args(argv)

    manifest = {'base_dir': args.out_dir, 'files': []}
    import_mappings = dict()
    for mapping in args.import_mappings:
        (src, dst) = mapping.split('|')
        import_mappings[src] = dst

    import_var_mappings = list()
    for mapping in args.import_var_mappings:
        (path, renaming) = mapping.split(':')
        (old_name, new_name) = renaming.split('|')
        import_var_mappings.append((path, (old_name, new_name)))

    # For `import_path`, either replace the prefix as described in
    #  `--import_mappings` or drop the "generated:" prefix.
    def _map_import(import_path):
        for regex, substitution in import_mappings.items():
            if regex[-1] == "/":
                substitution = rf"{substitution}\g<file>"
            rewritten = re.sub(rf"^{regex}(?P<file>.*)", substitution,
                               import_path)
            if rewritten != import_path:
                return rewritten

        return re.sub(r'^generated:(.*)', r'\g<1>', import_path)

    # Applies the rules from --import_var_mappings and returns the rewritten
    # import variables.
    def _map_import_vars(path, variables):
        for (map_path, (old, new)) in import_var_mappings:
            if map_path in path:
                # Rewrite direct imports:
                # import {html} from "lit/static-html" -->
                # import {staticHtml as html} from "lit/static-html"
                variables = re.sub(rf"\b({old})(\s*[,}}])", rf"{new} as \1\2",
                                   variables)
                # Rewrite aliased imports:
                # import {html as foo} from "lit/static-html" -->
                # import {staticHtml as foo} from "lit/static-html"
                variables = re.sub(rf"\b{old} (as \w+)", rf"{new} \1",
                                   variables)
                # Cleanup aliases:
                # import {staticHtml as staticHtml} from "lit/static-html" -->
                # import {staticHtml} from "lit/static-html"
                variables = variables.replace(f'{new} as {new}', new)
        return variables

    for f in args.in_files:
        output = []
        changed_lines_list = list()
        for line_no, line in enumerate(
                open(os.path.join(args.base_dir, f), 'r').readlines()):
            # Investigate JS parsing if this is insufficient.
            match = re.match(r'^(import .*["\'])(.*)(["\'];)$', line)
            if match:
                import_vars = match.group(1)
                import_path = match.group(2)
                new_import_path = _map_import(import_path)
                new_import_vars = _map_import_vars(import_path, import_vars)
                # If this is an import statement line and it has a replacement,
                # modify the line before outputing it.
                if new_import_path != import_path or new_import_vars != import_vars:
                    line = f"{new_import_vars}{new_import_path}{match.group(3)}\n"
                    generated_column = len(import_vars) + len(import_path)
                    # TODO(b/290142486): Also adjust the location of import var
                    # tokens.
                    rewritten_column = len(new_import_vars) + len(
                        new_import_path)
                    changed_line = {
                        "lineNum": line_no,
                        "generatedColumn": generated_column,
                        "rewrittenColumn": rewritten_column
                    }
                    changed_lines_list.append(changed_line)
            output.append(line)
        with open(os.path.join(args.out_dir, f), 'w') as out_file:
            out_file.write(''.join(output))
        manifest['files'].append(f)

        node.RunNode([str(_SOURCE_MAP_CREATOR), args.base_dir, f, args.out_dir, json.dumps(changed_lines_list)])

    with open(args.manifest_out, 'w') as manifest_file:
        json.dump(manifest, manifest_file)


if __name__ == '__main__':
    main(sys.argv[1:])