Browse Source

feat(TS_TYPECHECK): implementation
881bda4539ae182b7975c149191642ded49990e3

zaverden 11 months ago
parent
commit
ebc6526bcc

+ 0 - 2
build/conf/ts/ts.conf

@@ -75,8 +75,6 @@ macro _TS_CONFIG_EPILOGUE() {
     _GLOB(TS_GLOB_FILES $TS_GLOB_INCLUDE EXCLUDE $TS_GLOB_EXCLUDE)
 
     _GLOB(_TS_LINT_SRCS_VALUE **/*.(ts|tsx|js|jsx) EXCLUDE $TS_EXCLUDE_DIR_GLOB $TS_COMMON_OUTDIR_GLOB $TS_GLOB_EXCLUDE_ADDITIONAL)
-
-    _SETUP_EXTRACT_NODE_MODULES_RECIPE(${MODDIR})
 }
 
 # Used as inputs in TS_COMPILE through `$_AS_HIDDEN_INPUTS(IN $TS_INPUT_FILES)`

+ 16 - 2
build/conf/ts/ts_test.conf

@@ -105,15 +105,17 @@ macro _TS_TEST_FOR_ARGS(FOR_MOD, RELATIVE?"${CURDIR}":"${ARCADIA_ROOT}") {
 }
 
 macro _SETUP_EXTRACT_NODE_MODULES_RECIPE(FOR_PATH) {
-    DEPENDS(devtools/frontend_build_platform/nots/recipes/extract_node_modules)
     USE_RECIPE(devtools/frontend_build_platform/nots/recipes/extract_node_modules/recipe $FOR_PATH workspace_node_modules.tar)
 }
 
 macro _SETUP_EXTRACT_OUTPUT_TARS_RECIPE(FOR_PATH) {
-    DEPENDS(devtools/frontend_build_platform/nots/recipes/extract_output_tars)
     USE_RECIPE(devtools/frontend_build_platform/nots/recipes/extract_output_tars/recipe $FOR_PATH)
 }
 
+macro _SETUP_INSTALL_NODE_MODULES_RECIPE() {
+    USE_RECIPE(devtools/frontend_build_platform/nots/recipes/install_node_modules/recipe $NOTS_TOOL_BASE_ARGS)
+}
+
 
 ### @usage: TS_TEST_CONFIG(Path)
 ###
@@ -167,3 +169,15 @@ macro TS_TEST_DATA(RENAME="", GLOBS...) {
 macro TS_TEST_DEPENDS_ON_BUILD() {
     ENABLE(_TS_TEST_DEPENDS_ON_BUILD)
 }
+
+_TS_TYPECHECK_VALUE=none
+_TS_TYPECHECK_TSCONFIG=
+
+macro NO_TS_TYPECHECK() {
+    SET(_TS_TYPECHECK_VALUE none)
+}
+
+macro TS_TYPECHECK(TS_CONFG="") {
+    ENABLE(_TS_TYPECHECK_VALUE)
+    SET(_TS_TYPECHECK_TSCONFIG $TS_CONFG)
+}

+ 1 - 0
build/plugins/lib/test_const/__init__.py

@@ -74,6 +74,7 @@ STYLE_TEST_TYPES = [
     "flake8",
     "black",
     "ruff",
+    "tsc_typecheck",
 ]
 
 REGULAR_TEST_TYPES = [

+ 56 - 5
build/plugins/nots.py

@@ -2,7 +2,7 @@ import os
 
 import ymake
 import ytest
-from _common import get_norm_unit_path, rootrel_arc_src, to_yesno
+from _common import resolve_common_const, get_norm_unit_path, rootrel_arc_src, to_yesno
 
 
 # 1 is 60 files per chunk for TIMEOUT(60) - default timeout for SIZE(SMALL)
@@ -32,7 +32,7 @@ class PluginLogger(object):
                 parts.append(m if isinstance(m, str) else repr(m))
 
         # cyan color (code 36) for messages
-        return "\033[0;32m{}\033[0;49m \033[0;36m{}\033[0;49m".format(self.prefix, " ".join(parts))
+        return "\033[0;32m{}\033[0;49m\n\033[0;36m{}\033[0;49m".format(self.prefix, " ".join(parts))
 
     def info(self, *messages):
         if self.unit:
@@ -251,6 +251,7 @@ def on_ts_configure(unit, *tsconfig_paths):
         _filter_inputs_by_rules_from_tsconfig(unit, tsconfig)
 
     _setup_eslint(unit)
+    _setup_tsc_typecheck(unit, tsconfig_paths)
 
 
 def __set_append(unit, var_name, value):
@@ -362,6 +363,8 @@ def _setup_eslint(unit):
         return
 
     unit.on_peerdir_ts_resource("eslint")
+    user_recipes = unit.get("TEST_RECIPES_VALUE")
+    unit.on_setup_extract_node_modules_recipe(unit.get("MODDIR"))
 
     mod_dir = unit.get("MODDIR")
     lint_files = _resolve_module_files(unit, mod_dir, lint_files)
@@ -372,15 +375,54 @@ def _setup_eslint(unit):
     }
 
     _add_test(unit, "eslint", lint_files, deps, test_record, mod_dir)
+    unit.set(["TEST_RECIPES_VALUE", user_recipes])
+
+
+def _setup_tsc_typecheck(unit, tsconfig_paths: list[str]):
+    if not _is_tests_enabled(unit):
+        return
+
+    if unit.get("_TS_TYPECHECK_VALUE") == "none":
+        return
+
+    typecheck_files = ytest.get_values_list(unit, "TS_INPUT_FILES")
+    if not typecheck_files:
+        return
+
+    tsconfig_path = tsconfig_paths[0]
+
+    if len(tsconfig_paths) > 1:
+        tsconfig_path = unit.get("_TS_TYPECHECK_TSCONFIG")
+        if not tsconfig_path:
+            macros = " or ".join([f"TS_TYPECHECK({p})" for p in tsconfig_paths])
+            raise Exception(f"Module uses several tsconfig files, specify which one to use for typecheck: {macros}")
+        abs_tsconfig_path = unit.resolve(unit.resolve_arc_path(tsconfig_path))
+        if not abs_tsconfig_path:
+            raise Exception(f"tsconfig for typecheck not found: {tsconfig_path}")
+
+    unit.on_peerdir_ts_resource("typescript")
+    user_recipes = unit.get("TEST_RECIPES_VALUE")
+    unit.on_setup_install_node_modules_recipe()
+    unit.on_setup_extract_output_tars_recipe([unit.get("MODDIR")])
+
+    _add_test(
+        unit,
+        test_type="tsc_typecheck",
+        test_files=[resolve_common_const(f) for f in typecheck_files],
+        deps=_create_pm(unit).get_peers_from_package_json(),
+        test_record={"TS_CONFIG_PATH": tsconfig_path},
+        test_cwd=unit.get("MODDIR"),
+    )
+    unit.set(["TEST_RECIPES_VALUE", user_recipes])
 
 
 def _resolve_module_files(unit, mod_dir, file_paths):
+    mod_dir_with_sep_len = len(mod_dir) + 1
     resolved_files = []
 
     for path in file_paths:
         resolved = rootrel_arc_src(path, unit)
         if resolved.startswith(mod_dir):
-            mod_dir_with_sep_len = len(mod_dir) + 1
             resolved = resolved[mod_dir_with_sep_len:]
         resolved_files.append(resolved)
 
@@ -393,17 +435,26 @@ def _add_test(unit, test_type, test_files, deps=None, test_record=None, test_cwd
     def sort_uniq(text):
         return sorted(set(text))
 
+    recipes_lines = ytest.format_recipes(unit.get("TEST_RECIPES_VALUE")).strip().splitlines()
+    if recipes_lines:
+        deps = deps or []
+        deps.extend([os.path.dirname(r.strip().split(" ")[0]) for r in recipes_lines])
+
     if deps:
-        unit.ondepends(sort_uniq(deps))
+        joined_deps = "\n".join(deps)
+        logger.info(f"{test_type} deps: \n{joined_deps}")
+        unit.ondepends(deps)
 
     test_dir = get_norm_unit_path(unit)
     full_test_record = {
+        # Key to discover suite (see devtools/ya/test/explore/__init__.py#gen_suite)
+        "SCRIPT-REL-PATH": test_type,
+        # Test name as shown in PR check, should be unique inside one module
         "TEST-NAME": test_type.lower(),
         "TEST-TIMEOUT": unit.get("TEST_TIMEOUT") or "",
         "TEST-ENV": ytest.prepare_env(unit.get("TEST_ENV_VALUE")),
         "TESTED-PROJECT-NAME": os.path.splitext(unit.filename())[0],
         "TEST-RECIPES": ytest.prepare_recipes(unit.get("TEST_RECIPES_VALUE")),
-        "SCRIPT-REL-PATH": test_type,
         "SOURCE-FOLDER-PATH": test_dir,
         "BUILD-FOLDER-PATH": test_dir,
         "BINARY-PATH": os.path.join(test_dir, unit.filename()),

+ 10 - 2
build/plugins/ytest.py

@@ -43,10 +43,18 @@ def ontest_data(unit, *args):
     ymake.report_configure_error("TEST_DATA is removed in favour of DATA")
 
 
-def prepare_recipes(data):
+def format_recipes(data: str | None) -> str:
+    if not data:
+        return ""
+
     data = data.replace('"USE_RECIPE_DELIM"', "\n")
     data = data.replace("$TEST_RECIPES_VALUE", "")
-    return base64.b64encode(six.ensure_binary(data or ""))
+    return data
+
+
+def prepare_recipes(data: str | None) -> str:
+    formatted = format_recipes(data)
+    return base64.b64encode(six.ensure_binary(formatted))
 
 
 def prepare_env(data):