# Copyright 2024 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import annotations import re from typing import Optional from crossbench.flags.base import Flags class JSFlags(Flags): """Custom flags implementation for V8 flags (--js-flags in chrome) Additionally to the base Flag implementation it asserts that bool flags with the --no-.../--no... prefix are not contradicting each other. """ _NO_PREFIX = "--no" _NAME_RE = re.compile(r"--[a-zA-Z_][a-zA-Z0-9_-]+") # We allow two forms: # - space separated: --foo="1" --bar --baz='2' --boo=3 # - comma separated: --foo="1",--bar ,--baz='2', --boo=3 _VALUE_PATTERN = (r"('(?P[^',]+)')|" r"(\"(?P[^\",]+)\")|" r"(?P[^'\", =]+)") _END_OR_SEPARATOR_PATTERN = r"(\s*[,\s]\s*|$)" _PARSE_RE = re.compile(fr"(?P{_NAME_RE.pattern})" fr"((?P=)({_VALUE_PATTERN})?)?" fr"{_END_OR_SEPARATOR_PATTERN}") @classmethod def parse_str(cls, raw_flags: str) -> JSFlags: return cls._parse_str(raw_flags, "--js-flags") def copy(self) -> JSFlags: return self.__class__(self) def _set(self, flag_name: str, flag_value: Optional[str] = None, override: bool = False) -> None: self._validate_js_flag_name(flag_name) if flag_value is not None: self._validate_js_flag_value(flag_name, flag_value) self._check_negated_flag(flag_name, override) super()._set(flag_name, flag_value, override) def _validate_js_flag_value(self, flag_name: str, flag_value: str) -> None: if not isinstance(flag_value, str): raise TypeError("JSFlag value must be str, " f"but got {type(flag_value)}: {repr(flag_value)}") if "," in flag_value: raise ValueError( "--js-flags: Comma in V8 flag value, flag escaping for chrome's " f"--js-flags might not work: {flag_name}={repr(flag_value)}") if self._WHITE_SPACE_RE.search(flag_value): raise ValueError("--js-flags: V8 flag-values cannot contain whitespaces:" f"{flag_name}={repr(flag_value)}") def _validate_js_flag_name(self, flag_name: str) -> None: if not flag_name.startswith("--"): raise ValueError("--js-flags: Only long-form flag names allowed, " f"but got {repr(flag_name)}") if not self._NAME_RE.fullmatch(flag_name): raise ValueError(f"--js-flags: Invalid flag name {repr(flag_name)}. \n" "Check invalid characters in the V8 flag name?") def _check_negated_flag(self, flag_name: str, override: bool) -> None: if flag_name.startswith(self._NO_PREFIX): enabled = flag_name[len(self._NO_PREFIX):] # Check for --no-foo form if enabled.startswith("-"): enabled = enabled[1:] enabled = "--" + enabled if override: del self[enabled] elif enabled in self: raise ValueError( f"Conflicting flag {flag_name}, " f"it has already been enabled by {repr(self._describe(enabled))}") else: # --foo => --no-foo disabled = f"--no-{flag_name[2:]}" if disabled not in self: # Try compact version: --foo => --nofoo disabled = f"--no{flag_name[2:]}" if disabled not in self: return if override: del self[disabled] else: raise ValueError(f"Conflicting flag {flag_name}, " "it has previously been disabled by " f"{repr(self._describe(flag_name))}") def __str__(self) -> str: return ",".join(self)