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

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

37.33
/src/python/pants/core/goals/lint_goal.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
3✔
5

6
import logging
3✔
7
from collections import defaultdict
3✔
8
from collections.abc import Iterable, Iterator, Sequence
3✔
9
from typing import TypeVar
3✔
10

11
from pants.base.specs import Specs
3✔
12
from pants.core.goals.fix import AbstractFixRequest, convert_fix_result_to_lint_result, fix_batch
3✔
13
from pants.core.goals.lint import (
3✔
14
    AbstractLintRequest,
15
    Lint,
16
    LintFilesRequest,
17
    LintResult,
18
    LintSubsystem,
19
    LintTargetsRequest,
20
    get_partitions_by_request_type,
21
    lint_batch,
22
    partition_files,
23
    partition_targets,
24
)
25
from pants.core.goals.multi_tool_goal_helper import write_reports
3✔
26
from pants.core.util_rules.distdir import DistDir
3✔
27
from pants.engine.console import Console
3✔
28
from pants.engine.fs import PathGlobs, Workspace
3✔
29
from pants.engine.intrinsics import digest_to_snapshot
3✔
30
from pants.engine.rules import collect_rules, concurrently, goal_rule, implicitly, rule
3✔
31
from pants.engine.target import FieldSet
3✔
32
from pants.engine.unions import UnionMembership
3✔
33
from pants.util.collections import partition_sequentially
3✔
34
from pants.util.docutil import bin_name
3✔
35

36
logger = logging.getLogger(__name__)
3✔
37

38

39
_T = TypeVar("_T")
3✔
40

41

42
def _print_results(
3✔
43
    console: Console,
44
    results_by_tool: dict[str, list[LintResult]],
45
    formatter_failed: bool,
46
    fixer_failed: bool,
47
) -> None:
48
    if results_by_tool:
×
49
        console.print_stderr("")
×
50

51
    for tool_name in sorted(results_by_tool):
×
52
        results = results_by_tool[tool_name]
×
53
        if any(result.exit_code for result in results):
×
54
            sigil = console.sigil_failed()
×
55
            status = "failed"
×
56
        else:
57
            sigil = console.sigil_succeeded()
×
58
            status = "succeeded"
×
59
        console.print_stderr(f"{sigil} {tool_name} {status}.")
×
60

61
    if formatter_failed or fixer_failed:
×
62
        console.print_stderr("")
×
63

64
    if formatter_failed:
×
65
        console.print_stderr(f"(One or more formatters failed. Run `{bin_name()} fmt` to fix.)")
×
66

67
    if fixer_failed:
×
68
        console.print_stderr(f"(One or more fixers failed. Run `{bin_name()} fix` to fix.)")
×
69

70

71
def _get_error_code(results: Sequence[LintResult]) -> int:
3✔
72
    for result in reversed(results):
×
73
        if result.exit_code:
×
74
            return result.exit_code
×
75
    return 0
×
76

77

78
@rule
3✔
79
async def run_fixer_or_formatter_as_linter(batch: AbstractFixRequest.Batch) -> LintResult:
3✔
80
    # Note that AbstractFixRequest has AbstractFmtResult as a subtype (even though this doesn't
81
    # seem to make sense semantically), and fix_batch() operates on both.
82
    # TODO: Untangle, or at least make sense of, the complicated subtype relationships
83
    #  governing lint/fmt/fix, and document them. They are currently very hard to grok.
84
    #  See https://github.com/pantsbuild/pants/issues/22536.
85
    fix_result = await fix_batch(**implicitly({batch: AbstractFixRequest.Batch}))
×
86
    lint_result = await convert_fix_result_to_lint_result(fix_result)
×
87
    return lint_result
×
88

89

90
@goal_rule
3✔
91
async def lint(
3✔
92
    console: Console,
93
    workspace: Workspace,
94
    specs: Specs,
95
    lint_subsystem: LintSubsystem,
96
    union_membership: UnionMembership,
97
    dist_dir: DistDir,
98
) -> Lint:
99
    lint_request_types = union_membership.get(AbstractLintRequest)
×
100
    target_partitioners = union_membership.get(LintTargetsRequest.PartitionRequest)
×
101
    file_partitioners = union_membership.get(LintFilesRequest.PartitionRequest)
×
102

103
    partitions_by_request_type = await get_partitions_by_request_type(
×
104
        [
105
            request_type
106
            for request_type in lint_request_types
107
            if not (request_type.is_formatter and lint_subsystem.skip_formatters)
108
            and not (request_type.is_fixer and lint_subsystem.skip_fixers)
109
        ],
110
        target_partitioners,
111
        file_partitioners,
112
        lint_subsystem,
113
        specs,
114
        lambda request_type: partition_targets(
115
            **implicitly({request_type: LintTargetsRequest.PartitionRequest})
116
        ),
117
        lambda request_type: partition_files(
118
            **implicitly({request_type: LintFilesRequest.PartitionRequest})
119
        ),
120
    )
121

122
    if not partitions_by_request_type:
×
123
        return Lint(exit_code=0)
×
124

125
    def batch_by_size(iterable: Iterable[_T]) -> Iterator[tuple[_T, ...]]:
×
126
        batches = partition_sequentially(
×
127
            iterable,
128
            key=lambda x: str(x.address) if isinstance(x, FieldSet) else str(x),
129
            size_target=lint_subsystem.batch_size,
130
            size_max=4 * lint_subsystem.batch_size,
131
        )
132
        for batch in batches:
×
133
            yield tuple(batch)
×
134

135
    lint_batches_by_request_type = {
×
136
        request_type: [
137
            (batch, partition.metadata)
138
            for partitions in partitions_list
139
            for partition in partitions
140
            for batch in batch_by_size(partition.elements)
141
        ]
142
        for request_type, partitions_list in partitions_by_request_type.items()
143
    }
144

145
    formatter_snapshots = await concurrently(
×
146
        digest_to_snapshot(**implicitly({PathGlobs(elements): PathGlobs}))
147
        for request_type, batch in lint_batches_by_request_type.items()
148
        for elements, _ in batch
149
        if request_type._requires_snapshot
150
    )
151
    snapshots_iter = iter(formatter_snapshots)
×
152

153
    # Pairs of (batch, whether the batch is a fixer/formatter running as a linter).
154
    # In this context "running as a linter" means that we run it on a temp copy of the
155
    # files and diff the output to see if it passes or fails, without modifying the
156
    # original files. We may do this even when the fixer/formatter has a read-only lint
157
    # mode, so that, when the user requests the fix/fmt and lint goals, we don't have to run the
158
    # tool twice, once with read-only flags and once with "edit the files" flags.
159
    batches: Iterable[tuple[AbstractLintRequest.Batch, bool]] = [
×
160
        (
161
            request_type.Batch(
162
                request_type.tool_name,
163
                elements,
164
                key,
165
                **{"snapshot": next(snapshots_iter)} if request_type._requires_snapshot else {},
166
            ),
167
            request_type.is_fixer or request_type.is_formatter,
168
        )
169
        for request_type, batch in lint_batches_by_request_type.items()
170
        for elements, key in batch
171
    ]
172

173
    all_batch_results = await concurrently(
×
174
        [
175
            run_fixer_or_formatter_as_linter(batch)  # type:ignore[arg-type]
176
            if is_fixer_or_formatter
177
            else lint_batch(**implicitly({batch: AbstractLintRequest.Batch}))
178
            for batch, is_fixer_or_formatter in batches
179
        ]
180
    )
181

182
    core_request_types_by_batch_type = {
×
183
        request_type.Batch: request_type for request_type in lint_request_types
184
    }
185

186
    formatter_failed = any(
×
187
        result.exit_code
188
        for (batch, _), result in zip(batches, all_batch_results)
189
        if core_request_types_by_batch_type[type(batch)].is_formatter
190
    )
191

192
    fixer_failed = any(
×
193
        result.exit_code
194
        for (batch, _), result in zip(batches, all_batch_results)
195
        if core_request_types_by_batch_type[type(batch)].is_fixer
196
    )
197

198
    results_by_tool = defaultdict(list)
×
199
    for result in all_batch_results:
×
200
        results_by_tool[result.linter_name].append(result)
×
201

202
    write_reports(
×
203
        results_by_tool,
204
        workspace,
205
        dist_dir,
206
        goal_name=LintSubsystem.name,
207
    )
208

209
    _print_results(
×
210
        console,
211
        results_by_tool,
212
        formatter_failed,
213
        fixer_failed,
214
    )
215
    return Lint(_get_error_code(all_batch_results))
×
216

217

218
def rules():
3✔
219
    return collect_rules()
3✔
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

© 2026 Coveralls, Inc