| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Overview: ts_library() supports only a small subset of all possible tsconfig |
| # configurations. Some options cannot be used in general, and some options |
| # should only be set through the ts_library() gn args corresponding to them. In |
| # the latter case, there are requirements for how some of these args can be |
| # configured. This file contains validation logic for tsconfig files and the gn |
| # args corresponding to config options to limit the possibility of unsupported |
| # configurations proliferating in the codebase. |
| |
| import os |
| import pathlib |
| |
| from path_utils import isInAshFolder, getTargetPath |
| |
| _CWD = os.getcwd().replace('\\', '/') |
| _HERE_DIR = os.path.dirname(__file__) |
| _SRC_DIR = os.path.normpath(os.path.join(_HERE_DIR, '..', |
| '..')).replace('\\', '/') |
| |
| # Options configured by the ts_library should not be set separately. |
| _tsconfig_compiler_options_mappings = { |
| 'composite': 'composite=true', |
| 'declaration': 'composite=true', |
| 'inlineSourceMap': 'enable_source_maps=true', |
| 'inlineSources': 'enable_source_maps=true', |
| 'outDir': 'out_dir', |
| 'paths': 'path_mappings', |
| 'rootDir': 'root_dir', |
| 'tsBuildInfoFile': 'composite=true', |
| } |
| |
| # Allowed options within tsconfig_base.json |
| _allowed_config_options = [ |
| 'extends', |
| 'compilerOptions', |
| ] |
| |
| # Allowed compilerOptions. A 'None' value indicates that all values are allowed, |
| # otherwise only the set of specified values is allowed. |
| _allowed_compiler_options = { |
| 'allowUmdGlobalAccess': None, |
| 'isolatedModules': None, |
| 'lib': None, |
| 'module': ['NodeNext', 'preserve'], |
| 'moduleResolution': ['NodeNext', 'bundler'], |
| 'noPropertyAccessFromIndexSignature': None, |
| 'noUncheckedIndexedAccess': None, |
| 'noUncheckedSideEffectImports': None, |
| 'noUnusedLocals': None, |
| 'skipLibCheck': None, |
| 'strictPropertyInitialization': None, |
| 'target': ['ESNext', 'ES2024'], |
| 'typeRoots': None, |
| 'types': None, |
| } |
| |
| _ash_configs = [ |
| 'ash/webui/camera_app_ui/resources/tsconfig_base.json', |
| 'ash/webui/recorder_app_ui/resources/tsconfig_base.json', |
| 'chrome/browser/resources/chromeos/desk_api/tsconfig_base.json', |
| 'chrome/test/data/webui/chromeos/ash_common/tsconfig_base.json', |
| 'tools/typescript/tsconfig_base_polymer_cros.json', |
| 'third_party/cros-components/tsconfig_base.json', |
| ] |
| |
| def validateTsconfigJson(tsconfig, tsconfig_file, is_base_tsconfig): |
| # Special exception for material_web_components, which uses ts_library() |
| # in an unsupported way. |
| if 'third_party/material_web_components/tsconfig_base.json' in tsconfig_file: |
| return True, None |
| |
| # TODO(b/267329383): Migrate A11y to TypeScript. Accessibility code has |
| # different requirements for the migration because of this we need both |
| # allowjs and a custom tsconfig. |
| if 'accessibility/tsconfig.base.json' in tsconfig_file: |
| return True, None |
| |
| if not is_base_tsconfig: |
| for param in tsconfig.keys(): |
| if param not in _allowed_config_options: |
| return False, f'Invalid |{param}| option detected in ' + \ |
| f'{tsconfig_file}.Only |extends| and |compilerOptions| may ' + \ |
| 'be specified.' |
| |
| if 'compilerOptions' in tsconfig: |
| for param in _tsconfig_compiler_options_mappings.keys(): |
| if param in tsconfig['compilerOptions']: |
| tslibrary_flag = _tsconfig_compiler_options_mappings[param] |
| return False, f'Invalid |{param}| flag detected in {tsconfig_file}.' + \ |
| f' Use the dedicated |{tslibrary_flag}| attribute in '+ \ |
| 'ts_library() instead.' |
| |
| if 'ui/file_manager' in tsconfig_file: |
| # File manager uses ts_library() in an unsupported way. Just return true |
| # here for this special case. |
| return True, None |
| |
| if not is_base_tsconfig: |
| for param, param_value in tsconfig['compilerOptions'].items(): |
| if param not in _allowed_compiler_options: |
| if param != 'useDefineForClassFields' or \ |
| tsconfig_file not in _ash_configs: |
| return False, f'Disallowed |{param}| flag detected in '+ \ |
| 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'{allowed_values}.' |
| |
| return True, None |
| |
| |
| # Note 1: DO NOT add any directories here corresponding to newly added or |
| # existing TypeScript code. Instead use TypeScript, which is a requirement. |
| # Note 2: Only add a directory here if you are in the process of migrating |
| # legacy JS code to TS. Any new entries here should be accompanied by a bug |
| # tracking the TS migration. |
| def validateJavaScriptAllowed(source_dir, out_dir, platform): |
| # Special case for iOS, which sets the root src/ directory as the source |
| # directory for the ts_library() call, see |
| # ios/web/public/js_messaging/compile_ts.gni. |
| # We don't want to generally allow allow_js anywhere in src/ so check the |
| # output directory against the standard ios directories instead. This is a |
| # really broad check so also use the platform to make sure this is not abused |
| # elsewhere; the iOS use case of using allowJs broadly is not supported. |
| if platform == 'ios' and '/ios/' in out_dir: |
| return True, None |
| |
| if platform == 'chromeos_ash': |
| # Anything in these ChromeOS-specific directories is allowed to use allow_js. |
| # TODO (rbpotter): If possible, standardize the build setup in some of these |
| # folders such that they can be more accurately specified in the list below. |
| ash_directories = [ |
| 'ash/webui/annotator/resources/untrusted/', |
| 'ash/webui/camera_app_ui/', |
| 'ash/webui/color_internals/', |
| 'ash/webui/common/resources/', |
| 'ash/webui/file_manager/resources/labs/', |
| # TODO(b/314827247): Migrate media_app_ui to TypeScript and remove |
| # exception. |
| 'ash/webui/media_app_ui/', |
| # TODO(b/313562946): Migrate help_app_ui mojo pipeline to TypeScript and |
| # remove. |
| 'ash/webui/help_app_ui/', |
| # TODO(b/267329383): Migrate A11y to TypeScript. |
| 'chrome/browser/resources/chromeos/accessibility', |
| 'chrome/test/data/webui/chromeos', |
| 'chrome/test/data/webui/chromeos/ash_common', |
| 'chrome/test/data/webui/chromeos/nearby_share', |
| 'chrome/test/data/webui/cr_components/chromeos', |
| 'ui/file_manager/', |
| ] |
| for directory in ash_directories: |
| if directory in source_dir: |
| return True, None |
| |
| # Specific exceptions for directories that are still migrating to TS. |
| migrating_directories = [ |
| # TODO(crbug.com/385341235): Migrate inspect to TypeScript. |
| 'chrome/browser/resources/inspect', |
| 'chrome/browser/resources/net_internals', |
| 'chrome/test/data/webui', |
| # TODO(crbug.com/40848285): Migrate bluetooth-internals to TypeScript and |
| # remove exception. |
| 'chrome/test/data/webui/bluetooth_internals', |
| 'components/autofill/core/browser/autofill_and_password_manager_internals', |
| 'components/net_log/resources', |
| 'components/safe_browsing/content/browser/web_ui/resources', |
| 'components/translate/translate_internals', |
| 'content/browser/webrtc/resources', |
| 'ui/webui/resources/js', |
| 'ui/webui/resources/mojo', |
| |
| # TODO(crbug.com/40280699) : Migrate to TypeScript. |
| 'chrome/test/data/webui/media_internals', |
| 'content/browser/resources/media', |
| ] |
| for directory in migrating_directories: |
| if (source_dir.endswith(directory) |
| or source_dir.endswith(directory + '/preprocessed')): |
| return True, None |
| |
| return False, 'Invalid JS file detected for input directory ' + \ |
| f'{source_dir} and output directory {out_dir}, all new ' + \ |
| 'code should be added in TypeScript.' |
| |
| |
| def isMappingAllowed(is_ash_target, target_path, mapping_path): |
| if is_ash_target: |
| return True |
| |
| return not isInAshFolder(mapping_path) or target_path in exceptions |
| |
| |
| # TODO (https://www.crbug.com/1412158): Remove all exceptions below and this |
| # function; these build targets rely on implicitly unmapped dependencies. |
| def isUnsupportedJsTarget(gen_dir, root_gen_dir): |
| target_path = getTargetPath(gen_dir, root_gen_dir) |
| exceptions = [ |
| 'ash/webui/color_internals/resources', |
| 'chrome/browser/resources/chromeos/accessibility/select_to_speak', |
| ] |
| return target_path in exceptions |
| |
| |
| # |root_dir| shouldn't refer to any parent directories. Specifically it should |
| # be either: |
| # - within the folder tree starting at the ts_library() target's location |
| # - within the folder tree starting at the ts_library() target's corresponding |
| # target_gen_dir location. |
| def validateRootDir(root_dir, gen_dir, root_gen_dir, is_ios): |
| root_gen_dir_from_build = os.path.normpath(os.path.join( |
| gen_dir, root_gen_dir)).replace('\\', '/') |
| target_path = os.path.relpath(gen_dir, |
| root_gen_dir_from_build).replace('\\', '/') |
| |
| # Broadly special casing ios/ for now, since compile_ts.gni relies on |
| # unsupported behavior of setting the root_dir to src/. |
| # TODO (https://www.crbug.com/1412158): Make iOS TypeScript build tools use |
| # ts_library in a supported way, or change them to not rely on ts_library. |
| if (is_ios and 'ios' in pathlib.Path(target_path).parts): |
| return True, None |
| |
| # Legacy cases supported for backward-compatibility. Do not add new targets |
| # here. The existing exceptions should be removed over time. |
| exceptions = [ |
| # ChromeOS cases |
| 'ash/webui/color_internals/mojom', |
| ] |
| |
| if target_path in exceptions: |
| return True, None |
| |
| target_path_src = os.path.relpath(os.path.join(_SRC_DIR, target_path), |
| _CWD).replace('\\', '/') |
| root_path_from_gen = os.path.relpath(root_dir, |
| root_gen_dir_from_build).replace( |
| '\\', '/') |
| root_path_from_src = os.path.relpath(os.path.join(_CWD, root_dir), |
| _SRC_DIR).replace('\\', '/') |
| |
| if (root_path_from_gen.startswith(target_path) |
| or root_path_from_src.startswith(target_path)): |
| return True, None |
| |
| return False, f'Error: root_dir ({root_dir}) should be within {gen_dir} ' + \ |
| f'or {target_path_src}.' |
| |
| |
| def validateDefinitionDeps(definitions_files, target_path, gen_dir, |
| root_gen_dir, definitions): |
| # Root gen dir relative to the current working directory (essentially 'gen') |
| gen_dir_from_build = os.path.normpath(os.path.join(gen_dir, |
| root_gen_dir)).replace( |
| '\\', '/') |
| |
| def getPathFromCwd(exception): |
| return os.path.relpath(os.path.join(_SRC_DIR, exception), |
| _CWD).replace('\\', '/') |
| |
| # TODO(https://crbug.com/326005022): Determine if the following are actually |
| # safe for computation of gn input values. |
| exceptions_list = [ |
| 'third_party/d3/', |
| 'third_party/material_web_components/', |
| 'third_party/node/node_modules/', |
| 'third_party/polymer/v3_0/', |
| 'tools/typescript/tests/', |
| ] |
| exceptions = [getPathFromCwd(e) for e in exceptions_list] |
| definitions_normalized = [d.replace('\\', '/') for d in definitions] |
| |
| missing_inputs = [] |
| for f in definitions_files: |
| # File path relative to the current working directory. |
| f_from_cwd = os.path.relpath(f, _CWD).replace('\\', '/') |
| is_gen_file = f_from_cwd.startswith(gen_dir_from_build) |
| f_from_gen = os.path.relpath(f, gen_dir).replace('\\', '/') |
| if not is_gen_file and f_from_gen not in definitions_normalized and \ |
| not any(f_from_cwd.startswith(exception) for exception in exceptions): |
| missing_inputs.append( |
| os.path.relpath(f_from_cwd, _SRC_DIR).replace('\\', '/')) |
| |
| if not missing_inputs: |
| return True, None |
| |
| errorMessage = 'Undeclared dependencies to definition files encountered ' + \ |
| f'while building {target_path}. Please list the following ' + \ |
| 'file(s) in |definitions|:\n' |
| for missing_input in missing_inputs: |
| errorMessage += f'//{missing_input}\n' |
| |
| return False, errorMessage |