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