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:])
|