File: validate_tsconfig.py

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (306 lines) | stat: -rw-r--r-- 13,015 bytes parent folder | download | duplicates (2)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# 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,
    '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/browser/resources/chromeos/crostini_installer',
        'chrome/browser/resources/chromeos/crostini_upgrader',
        'chrome/browser/resources/chromeos/gaia_action_buttons',
        '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/40848285): Migrate bluetooth-internals to TypeScript and
      # remove exception.
      'chrome/browser/resources/bluetooth_internals',
      # TODO(crbug.com/41484340): Migrate to TypeScript.
      'chrome/browser/resources/device_log',
      # 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',
      # TODO(crbug.com/373951324): Migrate offline dino game to TypeScript.
      'components/neterror/resources',
      '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