0

WebUI: Validate all parent tsconfig files in ts_library().

Previously only the last tsconfig file in the inheritance chain would be
validated (usually specified with `tsconfig_base=` or automatically
inferred when not specified). Any parent files referenced via "extends:
..." would not be validated.

This is in preparation of walking the tsconfig inheritance chain to
automatically deduce when to use `skipLibCheck=true` to improve overall
TypeScript build time performance.

Bug: 397737230
Change-Id: I0ccd5fe3758fe915e437d8bc443fcb85a85c0159
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6300796
Commit-Queue: Demetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: Rebekah Potter <rbpotter@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1424909}
This commit is contained in:
dpapad
2025-02-25 17:46:56 -08:00
committed by Chromium LUCI CQ
parent 831cb31519
commit c7a7248d3c
4 changed files with 104 additions and 32 deletions

@ -0,0 +1,4 @@
{
"extends": "./tsconfig_base.json",
"compilerOptions": {}
}

@ -31,6 +31,14 @@ def _write_tsconfig_json(gen_dir, tsconfig, tsconfig_file):
json.dump(tsconfig, generated_tsconfig, indent=2)
return
# Normalize `input_path` from being relative to _CWD, to being relative to
# _SRC_DIR.
def _relative_to_src(input_path):
return os.path.relpath(os.path.normpath(os.path.join(_CWD, input_path)),
_SRC_DIR)
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('--deps', nargs='*')
@ -77,39 +85,60 @@ def main(argv):
tsconfig['compilerOptions'] = collections.OrderedDict()
with io.open(tsconfig_base_file, encoding='utf-8', mode='r') as f:
tsconfig_base = json.loads(f.read())
# Recursively iterate all inherited tsconfig files, walking up the
# inheritance chain.
parent_tsconfig_file = tsconfig_base_file
parent_tsconfig_counter = 0
while parent_tsconfig_file != None:
with io.open(parent_tsconfig_file, encoding='utf-8', mode='r') as f:
parent_tsconfig = json.loads(f.read())
is_base_tsconfig = args.tsconfig_base is None or \
args.tsconfig_base.endswith('/tools/typescript/tsconfig_base.json')
is_tsconfig_valid, error = validateTsconfigJson(tsconfig_base,
tsconfig_base_file,
is_base_tsconfig)
if not is_tsconfig_valid:
raise AssertionError(error)
# Validate each encountered tsconfig files against a set of constraints.
parent_tsconfig_file_normalized = _relative_to_src(parent_tsconfig_file)
is_base_tsconfig = parent_tsconfig_file_normalized.endswith(
os.path.normpath('tools/typescript/tsconfig_base.json'))
is_tsconfig_valid, error = validateTsconfigJson(
parent_tsconfig, parent_tsconfig_file_normalized, is_base_tsconfig)
if not is_tsconfig_valid:
raise AssertionError(error)
# Work-around for https://github.com/microsoft/TypeScript/issues/30024. Need
# to append 'trusted-types' in cases where the default configuration's
# 'types' field is overridden, because of the Chromium patch at
# third_party/node/typescript.patch
# TODO(dpapad): Remove if/when the TypeScript bug has been fixed.
if 'compilerOptions' in tsconfig_base and \
'types' in tsconfig_base['compilerOptions']:
types = tsconfig_base['compilerOptions']['types']
# Work-around for https://github.com/microsoft/TypeScript/issues/30024.
# Need to append 'trusted-types' in cases where the default
# configuration's 'types' field is overridden, because of the Chromium
# patch at third_party/node/patches/typescript.patch. Only look in the
# last tsconfig in the chain for any 'types' overrides as it seems
# sufficent since shared tsconfigs in tools/typescript/ already include
# 'trusted-types'.
# TODO(dpapad): Remove if/when the TypeScript bug has been fixed.
if parent_tsconfig_counter == 0:
if 'compilerOptions' in parent_tsconfig and \
'types' in parent_tsconfig['compilerOptions']:
types = parent_tsconfig['compilerOptions']['types']
if 'trusted-types' not in types:
# Ensure that typeRoots is not overridden in an incompatible way.
ERROR_MSG = ('Need to include \'third_party/node/node_modules/@types\' '
'when overriding the default typeRoots')
assert ('typeRoots' in tsconfig_base['compilerOptions']), ERROR_MSG
type_roots = tsconfig_base['compilerOptions']['typeRoots']
has_type_root = any(r.endswith('third_party/node/node_modules/@types') \
for r in type_roots)
assert has_type_root, ERROR_MSG
if 'trusted-types' not in types:
# Ensure that typeRoots is not overridden in an incompatible way.
ERROR_MSG = (
'Need to include \'third_party/node/node_modules/@types\' '
'when overriding the default typeRoots')
assert ('typeRoots'
in parent_tsconfig['compilerOptions']), ERROR_MSG
type_roots = parent_tsconfig['compilerOptions']['typeRoots']
has_type_root = any(r.endswith('third_party/node/node_modules/@types') \
for r in type_roots)
assert has_type_root, ERROR_MSG
augmented_types = types.copy()
augmented_types.append('trusted-types')
tsconfig['compilerOptions']['types'] = augmented_types
augmented_types = types.copy()
augmented_types.append('trusted-types')
tsconfig['compilerOptions']['types'] = augmented_types
# Calculate next step in the inheritance chain.
extends = parent_tsconfig.get('extends', None)
if extends != None:
parent_tsconfig_file = os.path.normpath(
os.path.join(os.path.dirname(parent_tsconfig_file), extends))
parent_tsconfig_counter += 1
else:
parent_tsconfig_file = None
tsconfig['compilerOptions']['rootDir'] = root_dir
tsconfig['compilerOptions']['outDir'] = out_dir

@ -434,7 +434,46 @@ class TsLibraryTest(unittest.TestCase):
])
except AssertionError as err:
self.assertTrue(
str(err).startswith('Invalid |composite| flag detected in '))
str(err).replace('\\', '/').startswith(
'Invalid |composite| flag detected in '
'tools/typescript/tests/project5/tsconfig_base.json.'
))
else:
self.fail('Failed to detect error')
# Test error case where the project's tsconfig file inherits from another
# tsconfig file that should fail validation.
def testTsConfigValidationErrorInParent(self):
self._out_folder = tempfile.mkdtemp(dir=_CWD)
root_dir = os.path.join(_HERE_DIR, 'tests', 'project5')
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
'project5')
try:
ts_library.main([
'--output_suffix',
'build_ts',
'--root_gen_dir',
os.path.relpath(self._out_folder, gen_dir),
'--root_src_dir',
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
'--root_dir',
os.path.relpath(root_dir, _CWD),
'--gen_dir',
os.path.relpath(gen_dir, _CWD),
'--out_dir',
os.path.relpath(gen_dir, _CWD),
'--in_files',
'bar.ts',
'--tsconfig_base',
os.path.relpath(os.path.join(root_dir, 'tsconfig_base2.json'),
gen_dir),
])
except AssertionError as err:
self.assertTrue(
str(err).replace('\\', '/').startswith(
'Invalid |composite| flag detected in '
'tools/typescript/tests/project5/tsconfig_base.json.'
))
else:
self.fail('Failed to detect error')

@ -93,12 +93,12 @@ def validateTsconfigJson(tsconfig, tsconfig_file, is_base_tsconfig):
for param, param_value in tsconfig['compilerOptions'].items():
if param not in _allowed_compiler_options:
return False, f'Disallowed |{param}| flag detected in '+ \
f'{tsconfig_file}.'
f'\'{tsconfig_file}\'.'
else:
allowed_values = _allowed_compiler_options[param]
if (allowed_values is not None and param_value not in allowed_values):
return False, f'Disallowed value |{param_value}| for |{param}| ' + \
f'flag detected in {tsconfig_file}. Must be one of ' + \
f'flag detected in \'{tsconfig_file}\'. Must be one of ' + \
f'{allowed_values}.'
return True, None