• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 18427419357

11 Oct 2025 09:11AM UTC coverage: 80.25%. First build
18427419357

Pull #22461

github

web-flow
Merge 1d59c7492 into 4846d0735
Pull Request #22461: Add typescript typechecking support

410 of 531 new or added lines in 10 files covered. (77.21%)

77631 of 96737 relevant lines covered (80.25%)

3.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.63
/src/python/pants/backend/typescript/goals/check_test.py
1
# Copyright 2025 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import json
1✔
7
import os
1✔
8
import textwrap
1✔
9
from pathlib import Path
1✔
10
from typing import cast
1✔
11

12
import pytest
1✔
13

14
from pants.backend.javascript import package_json
1✔
15
from pants.backend.javascript.subsystems import nodejs_tool
1✔
16
from pants.backend.javascript.target_types import JSSourcesGeneratorTarget
1✔
17
from pants.backend.tsx.target_types import TSXSourcesGeneratorTarget, TSXTestsGeneratorTarget
1✔
18
from pants.backend.typescript.goals import check
1✔
19
from pants.backend.typescript.goals.check import TypeScriptCheckFieldSet, TypeScriptCheckRequest
1✔
20
from pants.backend.typescript.target_types import (
1✔
21
    TypeScriptSourcesGeneratorTarget,
22
    TypeScriptTestsGeneratorTarget,
23
)
24
from pants.build_graph.address import Address
1✔
25
from pants.core.goals.check import CheckResults
1✔
26
from pants.core.target_types import FileTarget
1✔
27
from pants.engine.rules import QueryRule
1✔
28
from pants.testutil.rule_runner import RuleRunner
1✔
29

30
JS_TYPE_ERROR_FILE_NUMBER_TO_STRING = (
1✔
31
    'let x = "hello";\nx = 42; // Type error: cannot assign number to string\n'
32
)
33

34
TestProjectAndPackageManager = tuple[str, str]
1✔
35
RuleRunnerWithProjectAndPackageManager = tuple[RuleRunner, str, str]
1✔
36

37

38
@pytest.fixture(
1✔
39
    params=[("basic_project", "npm"), ("basic_project", "pnpm"), ("basic_project", "yarn")]
40
)
41
def basic_project_test(request) -> TestProjectAndPackageManager:
1✔
42
    return cast(TestProjectAndPackageManager, request.param)
1✔
43

44

45
@pytest.fixture(params=[("complex_project", "npm"), ("complex_project", "yarn")])
1✔
46
def complex_project_test(request) -> TestProjectAndPackageManager:
1✔
47
    return cast(TestProjectAndPackageManager, request.param)
1✔
48

49

50
@pytest.fixture(params=[("pnpm_link", "pnpm")])
1✔
51
def pnpm_project_test(request) -> TestProjectAndPackageManager:
1✔
52
    return cast(TestProjectAndPackageManager, request.param)
1✔
53

54

55
def _create_rule_runner(package_manager: str) -> RuleRunner:
1✔
56
    rule_runner = RuleRunner(
1✔
57
        rules=[
58
            *nodejs_tool.rules(),
59
            *check.rules(),
60
            QueryRule(CheckResults, [TypeScriptCheckRequest]),
61
        ],
62
        target_types=[
63
            package_json.PackageJsonTarget,
64
            JSSourcesGeneratorTarget,
65
            TypeScriptSourcesGeneratorTarget,
66
            TypeScriptTestsGeneratorTarget,
67
            TSXSourcesGeneratorTarget,
68
            TSXTestsGeneratorTarget,
69
            FileTarget,
70
        ],
71
        objects=dict(package_json.build_file_aliases().objects),
72
        preserve_tmpdirs=True,
73
    )
74
    rule_runner.set_options(
1✔
75
        [f"--nodejs-package-manager={package_manager}"],
76
        env_inherit={"PATH"},
77
    )
78
    return rule_runner
1✔
79

80

81
@pytest.fixture
1✔
82
def basic_rule_runner(
1✔
83
    basic_project_test: TestProjectAndPackageManager,
84
) -> RuleRunnerWithProjectAndPackageManager:
85
    test_project, package_manager = basic_project_test
1✔
86
    return _create_rule_runner(package_manager), test_project, package_manager
1✔
87

88

89
@pytest.fixture
1✔
90
def complex_proj_rule_runner(
1✔
91
    complex_project_test: TestProjectAndPackageManager,
92
) -> RuleRunnerWithProjectAndPackageManager:
93
    test_project, package_manager = complex_project_test
1✔
94
    return _create_rule_runner(package_manager), test_project, package_manager
1✔
95

96

97
@pytest.fixture
1✔
98
def pnpm_rule_runner(
1✔
99
    pnpm_project_test: TestProjectAndPackageManager,
100
) -> RuleRunnerWithProjectAndPackageManager:
101
    test_project, package_manager = pnpm_project_test
1✔
102
    return _create_rule_runner(package_manager), test_project, package_manager
1✔
103

104

105
def _parse_typescript_build_status(stdout: str) -> str:
1✔
106
    if (
1✔
107
        "is up to date but needs to update timestamps of output files that are older than input files"
108
        in stdout
109
    ):
110
        return "incremental_build"
1✔
111
    elif "Building project '" in stdout:
1✔
112
        return "full_build"
1✔
113
    else:
NEW
114
        return "no_verbose_output"
×
115

116

117
def _load_project_test_files(test_project: str) -> dict[str, str]:
1✔
118
    base_dir = Path(__file__).parent.parent / "test_resources" / test_project
1✔
119
    files = {}
1✔
120

121
    for file_path in base_dir.rglob("*"):
1✔
122
        if file_path.is_file():
1✔
123
            relative_path = file_path.relative_to(base_dir)
1✔
124
            files[f"{test_project}/{relative_path}"] = file_path.read_text()
1✔
125

126
    return files
1✔
127

128

129
def _override_index_ts_with_TS2345_error() -> str:
1✔
130
    return textwrap.dedent("""\
1✔
131
        import { add } from './math';
132

133
        export function calculate(): number {
134
            return add(5, "invalid"); // Type error: string not assignable to number
135
        }
136
    """)
137

138

139
def _override_math_ts_with_TS2322_error() -> str:
1✔
140
    return textwrap.dedent("""\
1✔
141
        export function add(a: number, b: number): number {
142
            return "not a number"; // This should cause TS2322 error
143
        }
144
    """)
145

146

147
def test_typescript_check_success(
1✔
148
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
149
) -> None:
150
    rule_runner, test_project, _ = basic_rule_runner
1✔
151

152
    test_files = _load_project_test_files(test_project)
1✔
153
    rule_runner.write_files(test_files)
1✔
154

155
    target = rule_runner.get_target(
1✔
156
        Address("basic_project/src", target_name="ts_sources", relative_file_path="index.ts")
157
    )
158
    field_set = TypeScriptCheckFieldSet.create(target)
1✔
159

160
    request = TypeScriptCheckRequest([field_set])
1✔
161
    results = rule_runner.request(CheckResults, [request])
1✔
162

163
    assert len(results.results) == 1
1✔
164
    assert results.results[0].exit_code == 0
1✔
165

166

167
def test_typescript_check_failure(
1✔
168
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
169
) -> None:
170
    rule_runner, test_project, _ = basic_rule_runner
1✔
171

172
    test_files = _load_project_test_files(test_project)
1✔
173
    test_files["basic_project/src/index.ts"] = _override_index_ts_with_TS2345_error()
1✔
174
    rule_runner.write_files(test_files)
1✔
175

176
    target = rule_runner.get_target(
1✔
177
        Address("basic_project/src", target_name="ts_sources", relative_file_path="index.ts")
178
    )
179
    field_set = TypeScriptCheckFieldSet.create(target)
1✔
180
    request = TypeScriptCheckRequest([field_set])
1✔
181
    results = rule_runner.request(CheckResults, [request])
1✔
182

183
    assert len(results.results) == 1
1✔
184
    result = results.results[0]
1✔
185
    assert result.exit_code != 0
1✔
186

187
    error_output = result.stdout + result.stderr
1✔
188
    assert "TS2345" in error_output
1✔
189
    assert "not assignable to parameter of type 'number'" in error_output
1✔
190

191

192
def test_typescript_check_fails_when_package_dep_fails(
1✔
193
    complex_proj_rule_runner: RuleRunnerWithProjectAndPackageManager,
194
) -> None:
195
    rule_runner, test_project, _ = complex_proj_rule_runner
1✔
196
    test_files = _load_project_test_files(test_project)
1✔
197

198
    test_files["complex_project/shared-utils/src/math.ts"] = _override_math_ts_with_TS2322_error()
1✔
199
    rule_runner.write_files(test_files)
1✔
200

201
    # Target main-app package but error is in shared-utils dependency
202
    main_target = rule_runner.get_target(
1✔
203
        Address("complex_project/main-app/src", relative_file_path="index.ts")
204
    )
205
    field_set = TypeScriptCheckFieldSet.create(main_target)
1✔
206

207
    request = TypeScriptCheckRequest([field_set])
1✔
208
    results = rule_runner.request(CheckResults, [request])
1✔
209

210
    assert len(results.results) == 1
1✔
211
    result = results.results[0]
1✔
212
    assert result.exit_code != 0, "Should fail due to type error in project dependency"
1✔
213

214
    error_output = result.stdout + result.stderr
1✔
215
    assert "TS2322" in error_output
1✔
216
    assert "not assignable to type 'number'" in error_output
1✔
217
    assert "shared-utils/src/math.ts" in error_output
1✔
218

219

220
def test_typescript_check_missing_outdir_validation(
1✔
221
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
222
) -> None:
223
    rule_runner, test_project, _ = basic_rule_runner
1✔
224

225
    test_files = _load_project_test_files(test_project)
1✔
226
    tsconfig_content = json.loads(test_files[f"{test_project}/tsconfig.json"])
1✔
227
    if "compilerOptions" in tsconfig_content and "outDir" in tsconfig_content["compilerOptions"]:
1✔
228
        del tsconfig_content["compilerOptions"]["outDir"]
1✔
229
    test_files[f"{test_project}/tsconfig.json"] = json.dumps(tsconfig_content, indent=2)
1✔
230
    rule_runner.write_files(test_files)
1✔
231

232
    target = rule_runner.get_target(
1✔
233
        Address(f"{test_project}/src", target_name="ts_sources", relative_file_path="index.ts")
234
    )
235
    field_set = TypeScriptCheckFieldSet.create(target)
1✔
236
    request = TypeScriptCheckRequest([field_set])
1✔
237

238
    with pytest.raises(Exception) as exc_info:
1✔
239
        rule_runner.request(CheckResults, [request])
1✔
240
    error_message = str(exc_info.value)
1✔
241
    assert "missing required 'outDir' setting" in error_message
1✔
242
    assert "TypeScript type-checking requires an explicit outDir" in error_message
1✔
243

244

245
def test_typescript_check_multiple_projects(
1✔
246
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
247
) -> None:
248
    rule_runner, _, _ = basic_rule_runner
1✔
249

250
    basic_project_files = _load_project_test_files("basic_project")
1✔
251
    test_files = {}
1✔
252

253
    # Project A - Independent project with its own package.json and tsconfig.json
254
    for file_path, content in basic_project_files.items():
1✔
255
        new_path = file_path.replace("basic_project", "project-a")
1✔
256
        if "package.json" in file_path:
1✔
257
            pkg_data = json.loads(content)
1✔
258
            pkg_data["name"] = "project-a"
1✔
259
            pkg_data["description"] = "Independent TypeScript project A"
1✔
260
            test_files[new_path] = json.dumps(pkg_data, indent=2)
1✔
261
        elif "index.ts" in file_path:
1✔
262
            test_files[new_path] = textwrap.dedent("""
1✔
263
                import { add } from './math';
264

265
                export function calculateA(): number {
266
                    return add(10, 20); // Project A calculation
267
                }
268
            """).strip()
269
        else:
270
            test_files[new_path] = content
1✔
271

272
    # Project B - Independent project with its own package.json and tsconfig.json
273
    for file_path, content in basic_project_files.items():
1✔
274
        new_path = file_path.replace("basic_project", "project-b")
1✔
275
        if "package.json" in file_path:
1✔
276
            pkg_data = json.loads(content)
1✔
277
            pkg_data["name"] = "project-b"
1✔
278
            pkg_data["description"] = "Independent TypeScript project B"
1✔
279
            test_files[new_path] = json.dumps(pkg_data, indent=2)
1✔
280
        elif "index.ts" in file_path:
1✔
281
            test_files[new_path] = textwrap.dedent("""
1✔
282
                import { add } from './math';
283

284
                export function calculateB(): number {
285
                    return add(5, 15); // Project B calculation
286
                }
287
            """).strip()
288
        else:
289
            test_files[new_path] = content
1✔
290

291
    rule_runner.write_files(test_files)
1✔
292

293
    project_a_target = rule_runner.get_target(
1✔
294
        Address("project-a/src", target_name="ts_sources", relative_file_path="index.ts")
295
    )
296
    project_b_target = rule_runner.get_target(
1✔
297
        Address("project-b/src", target_name="ts_sources", relative_file_path="index.ts")
298
    )
299
    project_a_field_set = TypeScriptCheckFieldSet.create(project_a_target)
1✔
300
    project_b_field_set = TypeScriptCheckFieldSet.create(project_b_target)
1✔
301

302
    request = TypeScriptCheckRequest([project_a_field_set, project_b_field_set])
1✔
303
    results = rule_runner.request(CheckResults, [request])
1✔
304

305
    assert len(results.results) == 2, f"Expected 2 projects, got {len(results.results)}"
1✔
306
    assert all(result.exit_code == 0 for result in results.results), (
1✔
307
        f"TypeScript compilation failed for projects: {[r.exit_code for r in results.results]}"
308
    )
309

310

311
def test_typescript_check_pnpm_link_protocol_success(
1✔
312
    pnpm_rule_runner: RuleRunnerWithProjectAndPackageManager,
313
) -> None:
314
    rule_runner, test_project, _ = pnpm_rule_runner
1✔
315
    test_files = _load_project_test_files(test_project)
1✔
316
    rule_runner.write_files(test_files)
1✔
317
    parent_target = rule_runner.get_target(Address("pnpm_link/src", relative_file_path="main.ts"))
1✔
318
    parent_field_set = TypeScriptCheckFieldSet.create(parent_target)
1✔
319

320
    request = TypeScriptCheckRequest([parent_field_set])
1✔
321
    results = rule_runner.request(CheckResults, [request])
1✔
322

323
    assert len(results.results) == 1
1✔
324
    assert results.results[0].exit_code == 0, (
1✔
325
        f"TypeScript check failed: {results.results[0].stdout}\n{results.results[0].stderr}"
326
    )
327

328

329
def test_file_targets_available_during_typescript_compilation(
1✔
330
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
331
) -> None:
332
    rule_runner, test_project, _ = basic_rule_runner
1✔
333

334
    test_files = _load_project_test_files(test_project)
1✔
335
    test_files.update(
1✔
336
        {
337
            # JSON data file to be provided by file() target
338
            f"{test_project}/config.json": json.dumps(
339
                {"message": "This content comes from a file target", "value": 42}
340
            ),
341
            # tsconfig.json that enables JSON imports and includes specific files
342
            f"{test_project}/tsconfig.json": json.dumps(
343
                {
344
                    "compilerOptions": {
345
                        "target": "ES2017",
346
                        "module": "commonjs",
347
                        "resolveJsonModule": True,
348
                        "esModuleInterop": True,
349
                        "declaration": True,
350
                        "composite": True,
351
                        "outDir": "./dist",
352
                    },
353
                    "include": ["src/index.ts", "config.json"],
354
                }
355
            ),
356
            # TypeScript source that imports the JSON file
357
            f"{test_project}/src/index.ts": textwrap.dedent("""
358
            // This import will fail if the file() target dependency is not working
359
            import config from '../config.json';
360

361
            // Type checking ensures the import resolved correctly
362
            const message: string = config.message;
363
            const value: number = config.value;
364

365
            console.log(`Message: ${message}, Value: ${value}`);
366
        """),
367
            # BUILD file with file target
368
            f"{test_project}/BUILD": textwrap.dedent("""
369
            package_json()
370

371
            # Provides config.json as a dependency
372
            file(name="config_data", source="config.json")
373
        """),
374
            # Override src/BUILD to include dependencies on the file target
375
            # Only include index.ts to avoid React errors from Button.tsx
376
            f"{test_project}/src/BUILD": textwrap.dedent(
377
                """
378
            # Single TypeScript source that depends on the file target in parent directory
379
            typescript_sources(
380
                name="ts_sources",
381
                sources=["index.ts"],
382
                dependencies=["//{test_project}:config_data"],
383
            )
384
        """.format(test_project=test_project)
385
            ),
386
        }
387
    )
388
    rule_runner.write_files(test_files)
1✔
389
    target = rule_runner.get_target(
1✔
390
        Address(f"{test_project}/src", target_name="ts_sources", relative_file_path="index.ts")
391
    )
392
    field_set = TypeScriptCheckFieldSet.create(target)
1✔
393
    request = TypeScriptCheckRequest([field_set])
1✔
394

395
    results = rule_runner.request(CheckResults, [request])
1✔
396
    assert len(results.results) == 1
1✔
397

398
    result = results.results[0]
1✔
399
    assert result.exit_code == 0, f"TypeScript compilation failed: {result.stdout}"
1✔
400
    assert "Cannot find module '../config.json'" not in result.stdout, (
1✔
401
        "File target dependency not working - TypeScript cannot find config.json"
402
    )
403

404

405
def test_typescript_incremental_compilation_cache(
1✔
406
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
407
) -> None:
408
    """Test TypeScript incremental compilation cache using comprehensive 4-run pattern.
409

410
    This test replicates the integration test scenarios using rule-runner approach:
411
    1. First run: Create cache, expect full build
412
    2. Second run: Use cache, expect incremental build (with --verbose/-v variation)
413
    3. Third run: After file modification, expect full rebuild
414
    4. Fourth run: After rebuild, expect incremental build again
415

416
    Uses --verbose/-v alternation to ensure Process cache invalidation.
417
    """
418
    rule_runner, test_project, package_manager = basic_rule_runner
1✔
419

420
    test_files = _load_project_test_files(test_project)
1✔
421
    rule_runner.write_files(test_files)
1✔
422
    target = rule_runner.get_target(
1✔
423
        Address(f"{test_project}/src", target_name="ts_sources", relative_file_path="index.ts")
424
    )
425
    field_set = TypeScriptCheckFieldSet.create(target)
1✔
426
    request = TypeScriptCheckRequest([field_set])
1✔
427

428
    # RUN 1: First compilation - should create cache and perform full build
429
    rule_runner.set_options(
1✔
430
        [
431
            f"--nodejs-package-manager={package_manager}",
432
            "--typescript-extra-build-args=['--verbose']",
433
        ],
434
        env_inherit={"PATH"},
435
    )
436
    results_1 = rule_runner.request(CheckResults, [request])
1✔
437
    assert len(results_1.results) == 1
1✔
438
    assert results_1.results[0].exit_code == 0, (
1✔
439
        f"First compilation failed: {results_1.results[0].stdout}\n{results_1.results[0].stderr}"
440
    )
441

442
    # Get cache directory
443
    with rule_runner.pushd():
1✔
444
        Path("BUILDROOT").touch()
1✔
445
        bootstrap_options = rule_runner.options_bootstrapper.bootstrap_options.for_global_scope()
1✔
446
    named_cache_dir = bootstrap_options.named_caches_dir
1✔
447
    typescript_cache_dir = os.path.join(str(named_cache_dir), "typescript_cache")
1✔
448

449
    # Verify cache directory and .tsbuildinfo files
450
    assert os.path.exists(typescript_cache_dir), (
1✔
451
        f"Cache directory not created: {typescript_cache_dir}"
452
    )
453
    tsbuildinfo_files = []
1✔
454
    for root, _, files in os.walk(typescript_cache_dir):
1✔
455
        for file in files:
1✔
456
            if file.endswith(".tsbuildinfo"):
1✔
457
                tsbuildinfo_files.append(os.path.join(root, file))
1✔
458

459
    assert len(tsbuildinfo_files) > 0, "No .tsbuildinfo files found in cache directory"
1✔
460

461
    # Verify first run behavior from TypeScript output
462
    combined_output1 = results_1.results[0].stdout + "\n" + results_1.results[0].stderr
1✔
463
    ts_status1 = _parse_typescript_build_status(combined_output1)
1✔
464
    assert ts_status1 == "full_build", (
1✔
465
        f"Expected full_build on first run, got: {ts_status1}. Output: {combined_output1}"
466
    )
467

468
    # RUN 2: Second compilation with argument variation - should use cache for incremental build
469

470
    # Change TypeScript args on same rule_runner for Process cache invalidation
471
    # This keeps the same build root so cache can be reused
472
    rule_runner.set_options(
1✔
473
        [
474
            f"--nodejs-package-manager={package_manager}",
475
            "--typescript-extra-build-args=['-v']",  # Use -v instead of --verbose
476
        ],
477
        env_inherit={"PATH"},
478
    )
479

480
    results_2 = rule_runner.request(CheckResults, [request])
1✔
481
    assert len(results_2.results) == 1
1✔
482
    assert results_2.results[0].exit_code == 0, (
1✔
483
        f"Second compilation failed: {results_2.results[0].stdout}\n{results_2.results[0].stderr}"
484
    )
485

486
    combined_output2 = results_2.results[0].stdout + "\n" + results_2.results[0].stderr
1✔
487
    ts_status2 = _parse_typescript_build_status(combined_output2)
1✔
488
    assert ts_status2 == "incremental_build", (
1✔
489
        f"Expected incremental_build on second run, got: {ts_status2}. Output: {combined_output2}"
490
    )
491

492
    # RUN 3: File modification - should trigger rebuild
493
    modified_index_content = textwrap.dedent("""
1✔
494
        import { add } from './math';
495

496
        export function calculate(): number {
497
            // Modified: added comment to trigger cache invalidation
498
            return add(2, 3);
499
        }
500
    """).strip()
501
    test_files[f"{test_project}/src/index.ts"] = modified_index_content
1✔
502

503
    rule_runner.set_options(
1✔
504
        [
505
            f"--nodejs-package-manager={package_manager}",
506
            "--typescript-extra-build-args=['--verbose']",  # Back to --verbose
507
        ],
508
        env_inherit={"PATH"},
509
    )
510
    rule_runner.write_files(test_files)  # Write modified files
1✔
511
    results_3 = rule_runner.request(CheckResults, [request])
1✔
512

513
    assert len(results_3.results) == 1
1✔
514
    assert results_3.results[0].exit_code == 0, (
1✔
515
        f"Third compilation failed: {results_3.results[0].stdout}\n{results_3.results[0].stderr}"
516
    )
517
    combined_output3 = results_3.results[0].stdout + "\n" + results_3.results[0].stderr
1✔
518
    ts_status3 = _parse_typescript_build_status(combined_output3)
1✔
519
    assert ts_status3 == "full_build", (
1✔
520
        f"Expected full_build after file modification, got: {ts_status3}. Output: {combined_output3}"
521
    )
522

523
    # RUN 4: After modification - should use cache again
524
    rule_runner.set_options(
1✔
525
        [
526
            f"--nodejs-package-manager={package_manager}",
527
            "--typescript-extra-build-args=['-v']",  # Back to -v
528
        ],
529
        env_inherit={"PATH"},
530
    )
531

532
    results_4 = rule_runner.request(CheckResults, [request])
1✔
533

534
    assert len(results_4.results) == 1
1✔
535
    assert results_4.results[0].exit_code == 0, (
1✔
536
        f"Fourth compilation failed: {results_4.results[0].stdout}\n{results_4.results[0].stderr}"
537
    )
538
    combined_output4 = results_4.results[0].stdout + "\n" + results_4.results[0].stderr
1✔
539
    ts_status4 = _parse_typescript_build_status(combined_output4)
1✔
540
    assert ts_status4 == "incremental_build", (
1✔
541
        f"Expected incremental_build after rebuild, got: {ts_status4}. Output: {combined_output4}"
542
    )
543

544

545
def test_setting_typescript_version_emits_warning(
1✔
546
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager, caplog
547
) -> None:
548
    rule_runner, test_project, package_manager = basic_rule_runner
1✔
549
    test_files = _load_project_test_files(test_project)
1✔
550
    rule_runner.write_files(test_files)
1✔
551

552
    rule_runner.set_options(
1✔
553
        [f"--nodejs-package-manager={package_manager}", "--typescript-version=typescript@5.0.0"],
554
        env_inherit={"PATH"},
555
    )
556

557
    target = rule_runner.get_target(
1✔
558
        Address("basic_project/src", target_name="ts_sources", relative_file_path="index.ts")
559
    )
560
    request = TypeScriptCheckRequest(field_sets=(TypeScriptCheckFieldSet.create(target),))
1✔
561
    rule_runner.request(CheckResults, [request])
1✔
562

563
    assert caplog.records
1✔
564
    assert "You set --typescript-version=typescript@5.0.0" in caplog.text
1✔
565
    assert "This setting is ignored because TypeScript always uses" in caplog.text
1✔
566

567

568
def test_check_javascript_enabled_via_tsconfig(
1✔
569
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
570
) -> None:
571
    rule_runner, test_project, _ = basic_rule_runner
1✔
572
    test_files = _load_project_test_files(test_project)
1✔
573

574
    tsconfig_content = json.loads(test_files["basic_project/tsconfig.json"])
1✔
575
    tsconfig_content["compilerOptions"]["allowJs"] = True
1✔
576
    tsconfig_content["compilerOptions"]["checkJs"] = True
1✔
577
    test_files["basic_project/tsconfig.json"] = json.dumps(tsconfig_content, indent=2)
1✔
578

579
    test_files["basic_project/src/error.js"] = JS_TYPE_ERROR_FILE_NUMBER_TO_STRING
1✔
580
    test_files["basic_project/src/BUILD"] = (
1✔
581
        'javascript_sources(name="js_sources")\ntypescript_sources()\n'
582
    )
583

584
    rule_runner.write_files(test_files)
1✔
585

586
    js_target = rule_runner.get_target(
1✔
587
        Address("basic_project/src", target_name="js_sources", relative_file_path="error.js")
588
    )
589
    request = TypeScriptCheckRequest(field_sets=(TypeScriptCheckFieldSet.create(js_target),))
1✔
590
    results = rule_runner.request(CheckResults, [request])
1✔
591

592
    assert len(results.results) == 1
1✔
593
    assert results.results[0].exit_code != 0
1✔
594
    assert "error.js" in results.results[0].stdout
1✔
595
    assert "Type 'number' is not assignable to type 'string'" in results.results[0].stdout
1✔
596

597

598
def test_check_javascript_disabled_via_tsconfig(
1✔
599
    basic_rule_runner: RuleRunnerWithProjectAndPackageManager,
600
) -> None:
601
    rule_runner, test_project, _ = basic_rule_runner
1✔
602
    test_files = _load_project_test_files(test_project)
1✔
603

604
    # Use the same JS file with type error that would be caught if processed
605
    test_files["basic_project/src/error.js"] = JS_TYPE_ERROR_FILE_NUMBER_TO_STRING
1✔
606
    test_files["basic_project/src/BUILD"] = (
1✔
607
        'javascript_sources(name="js_sources")\ntypescript_sources()\n'
608
    )
609

610
    rule_runner.write_files(test_files)
1✔
611

612
    js_target = rule_runner.get_target(
1✔
613
        Address("basic_project/src", target_name="js_sources", relative_file_path="error.js")
614
    )
615
    request = TypeScriptCheckRequest(field_sets=(TypeScriptCheckFieldSet.create(js_target),))
1✔
616
    results = rule_runner.request(CheckResults, [request])
1✔
617

618
    assert len(results.results) == 1
1✔
619
    assert results.results[0].exit_code == 0
1✔
620
    assert "error.js" not in results.results[0].stdout
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc