python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
import zlib
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .agent_protocol import make_target_id
|
|
10
|
+
from .agent_workspace import (
|
|
11
|
+
AgentWorkspace,
|
|
12
|
+
unit_display_path,
|
|
13
|
+
unit_source_path,
|
|
14
|
+
unit_target_id,
|
|
15
|
+
)
|
|
16
|
+
from .metrics import (
|
|
17
|
+
MetricProblem,
|
|
18
|
+
ProjectMetrics,
|
|
19
|
+
UnitMetrics,
|
|
20
|
+
aggregate_project_metrics,
|
|
21
|
+
analyze_include,
|
|
22
|
+
analyze_unit,
|
|
23
|
+
)
|
|
24
|
+
from .parallel_outline import resolve_worker_count, run_outline_tasks
|
|
25
|
+
from .parser_backend import ParserMode
|
|
26
|
+
from .preprocessor import IncludeLoader
|
|
27
|
+
from .project_config import ProjectPathConfig, workspace_include_loader
|
|
28
|
+
from .source_reader import read_source_text
|
|
29
|
+
|
|
30
|
+
_LARGE_PROJECT_UNIT_THRESHOLD = 256
|
|
31
|
+
_LARGE_PROJECT_BYTE_THRESHOLD = 16 * 1024 * 1024
|
|
32
|
+
_METRIC_TASK_BATCH_SIZE = 4
|
|
33
|
+
_LARGE_METRIC_TASK_THRESHOLD = 256
|
|
34
|
+
_LARGE_METRIC_BATCHES_PER_WORKER = 12
|
|
35
|
+
MetricProgressCallback = Callable[[int, int, str], None]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class _MetricTask:
|
|
40
|
+
ordinal: int
|
|
41
|
+
source_path: str
|
|
42
|
+
display_path: str
|
|
43
|
+
unit_id: str
|
|
44
|
+
defines: tuple[str, ...]
|
|
45
|
+
include_paths: tuple[str, ...]
|
|
46
|
+
parser_mode: ParserMode
|
|
47
|
+
project_config: ProjectPathConfig | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class _MetricResult:
|
|
52
|
+
ordinal: int
|
|
53
|
+
source_path: str
|
|
54
|
+
metrics: UnitMetrics | None
|
|
55
|
+
problem: MetricProblem | None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class _MetricTaskBatch:
|
|
60
|
+
ordinal: int
|
|
61
|
+
source_path: str
|
|
62
|
+
tasks: tuple[_MetricTask, ...]
|
|
63
|
+
compress_vocabulary: bool = False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True, slots=True)
|
|
67
|
+
class _MetricResultBatch:
|
|
68
|
+
ordinal: int
|
|
69
|
+
source_path: str
|
|
70
|
+
results: tuple[_MetricResult, ...]
|
|
71
|
+
vocabulary_blob: bytes = b""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _analyze_metric_task(task: _MetricTask) -> _MetricResult:
|
|
75
|
+
return _analyze_metric_task_with_loader(
|
|
76
|
+
task,
|
|
77
|
+
workspace_include_loader(
|
|
78
|
+
task.project_config,
|
|
79
|
+
task.include_paths,
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _analyze_metric_task_with_loader(
|
|
85
|
+
task: _MetricTask,
|
|
86
|
+
include_loader: IncludeLoader | None,
|
|
87
|
+
) -> _MetricResult:
|
|
88
|
+
try:
|
|
89
|
+
source = read_source_text(Path(task.source_path))
|
|
90
|
+
except (OSError, UnicodeError):
|
|
91
|
+
return _MetricResult(
|
|
92
|
+
task.ordinal,
|
|
93
|
+
task.source_path,
|
|
94
|
+
None,
|
|
95
|
+
MetricProblem(
|
|
96
|
+
kind="source_unavailable",
|
|
97
|
+
path=task.display_path,
|
|
98
|
+
message="Could not read source.",
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
metrics = analyze_unit(
|
|
102
|
+
source,
|
|
103
|
+
task.display_path,
|
|
104
|
+
defines=task.defines,
|
|
105
|
+
include_paths=task.include_paths,
|
|
106
|
+
include_loader=include_loader,
|
|
107
|
+
parser_mode=task.parser_mode,
|
|
108
|
+
)
|
|
109
|
+
return _MetricResult(
|
|
110
|
+
task.ordinal,
|
|
111
|
+
task.source_path,
|
|
112
|
+
replace(metrics, unit_id=task.unit_id),
|
|
113
|
+
None,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _analyze_metric_batch(batch: _MetricTaskBatch) -> _MetricResultBatch:
|
|
118
|
+
results: list[_MetricResult] = []
|
|
119
|
+
operators: set[str] = set()
|
|
120
|
+
operands: set[str] = set()
|
|
121
|
+
vocabulary_result_index: int | None = None
|
|
122
|
+
include_loaders: dict[
|
|
123
|
+
tuple[ProjectPathConfig | None, tuple[str, ...]],
|
|
124
|
+
IncludeLoader | None,
|
|
125
|
+
] = {}
|
|
126
|
+
for task in batch.tasks:
|
|
127
|
+
context = (task.project_config, task.include_paths)
|
|
128
|
+
if context not in include_loaders:
|
|
129
|
+
include_loaders[context] = workspace_include_loader(*context)
|
|
130
|
+
result = _analyze_metric_task_with_loader(task, include_loaders[context])
|
|
131
|
+
if result.metrics is not None:
|
|
132
|
+
operators.update(result.metrics._operator_vocabulary)
|
|
133
|
+
operands.update(result.metrics._operand_vocabulary)
|
|
134
|
+
if vocabulary_result_index is None:
|
|
135
|
+
vocabulary_result_index = len(results)
|
|
136
|
+
result = replace(
|
|
137
|
+
result,
|
|
138
|
+
metrics=replace(
|
|
139
|
+
result.metrics,
|
|
140
|
+
_operator_vocabulary=frozenset(),
|
|
141
|
+
_operand_vocabulary=frozenset(),
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
results.append(result)
|
|
145
|
+
vocabulary_blob = b""
|
|
146
|
+
if vocabulary_result_index is not None and batch.compress_vocabulary:
|
|
147
|
+
vocabulary_blob = zlib.compress(
|
|
148
|
+
pickle.dumps(
|
|
149
|
+
(frozenset(operators), frozenset(operands)),
|
|
150
|
+
protocol=pickle.HIGHEST_PROTOCOL,
|
|
151
|
+
),
|
|
152
|
+
level=1,
|
|
153
|
+
)
|
|
154
|
+
elif vocabulary_result_index is not None:
|
|
155
|
+
vocabulary_result = results[vocabulary_result_index]
|
|
156
|
+
assert vocabulary_result.metrics is not None
|
|
157
|
+
results[vocabulary_result_index] = replace(
|
|
158
|
+
vocabulary_result,
|
|
159
|
+
metrics=replace(
|
|
160
|
+
vocabulary_result.metrics,
|
|
161
|
+
_operator_vocabulary=frozenset(operators),
|
|
162
|
+
_operand_vocabulary=frozenset(operands),
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
return _MetricResultBatch(
|
|
166
|
+
ordinal=batch.ordinal,
|
|
167
|
+
source_path=batch.source_path,
|
|
168
|
+
results=tuple(results),
|
|
169
|
+
vocabulary_blob=vocabulary_blob,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _metric_task_batches(
|
|
174
|
+
tasks: Iterable[_MetricTask],
|
|
175
|
+
*,
|
|
176
|
+
batch_count: int | None = None,
|
|
177
|
+
) -> tuple[_MetricTaskBatch, ...]:
|
|
178
|
+
task_list = tuple(tasks)
|
|
179
|
+
if not task_list:
|
|
180
|
+
return ()
|
|
181
|
+
selected_batch_count = batch_count or (
|
|
182
|
+
(len(task_list) + _METRIC_TASK_BATCH_SIZE - 1) // _METRIC_TASK_BATCH_SIZE
|
|
183
|
+
)
|
|
184
|
+
selected_batch_count = min(max(1, selected_batch_count), len(task_list))
|
|
185
|
+
buckets: list[list[_MetricTask]] = [[] for _ in range(selected_batch_count)]
|
|
186
|
+
sizes: dict[str, int] = {}
|
|
187
|
+
for task in task_list:
|
|
188
|
+
try:
|
|
189
|
+
sizes[task.source_path] = Path(task.source_path).stat().st_size
|
|
190
|
+
except OSError:
|
|
191
|
+
sizes[task.source_path] = -1
|
|
192
|
+
ordered = sorted(
|
|
193
|
+
task_list,
|
|
194
|
+
key=lambda task: (-sizes[task.source_path], task.ordinal),
|
|
195
|
+
)
|
|
196
|
+
bucket_sizes = [0] * selected_batch_count
|
|
197
|
+
for index, task in enumerate(ordered):
|
|
198
|
+
if batch_count is None:
|
|
199
|
+
bucket_index = index % selected_batch_count
|
|
200
|
+
else:
|
|
201
|
+
bucket_index = min(
|
|
202
|
+
range(selected_batch_count),
|
|
203
|
+
key=lambda candidate: (bucket_sizes[candidate], candidate),
|
|
204
|
+
)
|
|
205
|
+
buckets[bucket_index].append(task)
|
|
206
|
+
bucket_sizes[bucket_index] += max(0, sizes[task.source_path])
|
|
207
|
+
return tuple(
|
|
208
|
+
_MetricTaskBatch(
|
|
209
|
+
ordinal=index,
|
|
210
|
+
source_path=bucket[0].source_path,
|
|
211
|
+
tasks=tuple(bucket),
|
|
212
|
+
compress_vocabulary=batch_count is not None,
|
|
213
|
+
)
|
|
214
|
+
for index, bucket in enumerate(buckets)
|
|
215
|
+
if bucket
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _run_metric_tasks(
|
|
220
|
+
tasks: Iterable[_MetricTask],
|
|
221
|
+
*,
|
|
222
|
+
configured_workers: int,
|
|
223
|
+
memory_budget_bytes: int | None = None,
|
|
224
|
+
on_complete: Callable[[_MetricResult], None],
|
|
225
|
+
cancel_check: Callable[[], None] | None = None,
|
|
226
|
+
) -> None:
|
|
227
|
+
if cancel_check is not None:
|
|
228
|
+
cancel_check()
|
|
229
|
+
task_list = tuple(tasks)
|
|
230
|
+
batch_count = None
|
|
231
|
+
if (
|
|
232
|
+
len(task_list) >= _LARGE_METRIC_TASK_THRESHOLD
|
|
233
|
+
and configured_workers != 1
|
|
234
|
+
):
|
|
235
|
+
batch_count = (
|
|
236
|
+
resolve_worker_count(
|
|
237
|
+
configured_workers,
|
|
238
|
+
task_count=len(task_list),
|
|
239
|
+
memory_budget_bytes=memory_budget_bytes,
|
|
240
|
+
)
|
|
241
|
+
* _LARGE_METRIC_BATCHES_PER_WORKER
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def consume_batch(batch: _MetricResultBatch) -> None:
|
|
245
|
+
if cancel_check is not None:
|
|
246
|
+
cancel_check()
|
|
247
|
+
results = list(batch.results)
|
|
248
|
+
if batch.vocabulary_blob:
|
|
249
|
+
operators, operands = pickle.loads(zlib.decompress(batch.vocabulary_blob))
|
|
250
|
+
for index, result in enumerate(results):
|
|
251
|
+
if result.metrics is None:
|
|
252
|
+
continue
|
|
253
|
+
results[index] = replace(
|
|
254
|
+
result,
|
|
255
|
+
metrics=replace(
|
|
256
|
+
result.metrics,
|
|
257
|
+
_operator_vocabulary=operators,
|
|
258
|
+
_operand_vocabulary=operands,
|
|
259
|
+
),
|
|
260
|
+
)
|
|
261
|
+
break
|
|
262
|
+
for result in results:
|
|
263
|
+
if cancel_check is not None:
|
|
264
|
+
cancel_check()
|
|
265
|
+
on_complete(result)
|
|
266
|
+
|
|
267
|
+
run_outline_tasks(
|
|
268
|
+
_metric_task_batches(task_list, batch_count=batch_count),
|
|
269
|
+
configured_workers=configured_workers,
|
|
270
|
+
memory_budget_bytes=memory_budget_bytes,
|
|
271
|
+
on_complete=consume_batch,
|
|
272
|
+
retain_results=False,
|
|
273
|
+
task_runner=_analyze_metric_batch,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def build_workspace_metrics(
|
|
278
|
+
workspace: AgentWorkspace,
|
|
279
|
+
*,
|
|
280
|
+
workers: int = 0,
|
|
281
|
+
worker_memory_budget_bytes: int | None = None,
|
|
282
|
+
cancel_check: Callable[[], None] | None = None,
|
|
283
|
+
) -> ProjectMetrics:
|
|
284
|
+
units = tuple(workspace.units)
|
|
285
|
+
source_bytes = 0
|
|
286
|
+
for unit in units:
|
|
287
|
+
if cancel_check is not None:
|
|
288
|
+
cancel_check()
|
|
289
|
+
try:
|
|
290
|
+
source_bytes += unit_source_path(workspace.root, unit).stat().st_size
|
|
291
|
+
except OSError:
|
|
292
|
+
continue
|
|
293
|
+
parser_mode = (
|
|
294
|
+
ParserMode.TOLERANT
|
|
295
|
+
if len(units) >= _LARGE_PROJECT_UNIT_THRESHOLD
|
|
296
|
+
or source_bytes >= _LARGE_PROJECT_BYTE_THRESHOLD
|
|
297
|
+
else ParserMode.STRICT
|
|
298
|
+
)
|
|
299
|
+
analyzed_units: list[UnitMetrics] = []
|
|
300
|
+
include_metrics: list[UnitMetrics] = []
|
|
301
|
+
problems: list[MetricProblem] = []
|
|
302
|
+
|
|
303
|
+
def consume_result(result: _MetricResult) -> None:
|
|
304
|
+
if result.metrics is not None:
|
|
305
|
+
analyzed_units.append(result.metrics)
|
|
306
|
+
if result.problem is not None:
|
|
307
|
+
problems.append(result.problem)
|
|
308
|
+
|
|
309
|
+
_run_metric_tasks(
|
|
310
|
+
(
|
|
311
|
+
_MetricTask(
|
|
312
|
+
ordinal,
|
|
313
|
+
str(unit_source_path(workspace.root, unit)),
|
|
314
|
+
unit_display_path(workspace.root, unit),
|
|
315
|
+
unit_target_id(workspace.root, unit),
|
|
316
|
+
workspace.defines,
|
|
317
|
+
workspace.include_paths,
|
|
318
|
+
parser_mode,
|
|
319
|
+
workspace.project_config,
|
|
320
|
+
)
|
|
321
|
+
for ordinal, unit in enumerate(units)
|
|
322
|
+
),
|
|
323
|
+
configured_workers=workers,
|
|
324
|
+
memory_budget_bytes=worker_memory_budget_bytes,
|
|
325
|
+
on_complete=consume_result,
|
|
326
|
+
cancel_check=cancel_check,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
include_loc = 0
|
|
330
|
+
seen_includes: set[str] = set()
|
|
331
|
+
for include_file in workspace.include_files:
|
|
332
|
+
if cancel_check is not None:
|
|
333
|
+
cancel_check()
|
|
334
|
+
recorded_path = include_file["path"]
|
|
335
|
+
display_path = workspace.include_display_path(recorded_path)
|
|
336
|
+
normalized_path = str(display_path).replace("\\", "/").casefold()
|
|
337
|
+
if normalized_path in seen_includes:
|
|
338
|
+
continue
|
|
339
|
+
seen_includes.add(normalized_path)
|
|
340
|
+
source_path = workspace.include_source_path(recorded_path)
|
|
341
|
+
try:
|
|
342
|
+
include_source = read_source_text(source_path)
|
|
343
|
+
item = replace(
|
|
344
|
+
analyze_include(include_source, display_path),
|
|
345
|
+
unit_id=make_target_id(
|
|
346
|
+
"unit",
|
|
347
|
+
display_path,
|
|
348
|
+
Path(include_file["name"]).stem,
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
include_metrics.append(item)
|
|
352
|
+
include_loc += item.lines.total_lines
|
|
353
|
+
except (OSError, UnicodeError):
|
|
354
|
+
problems.append(
|
|
355
|
+
MetricProblem(
|
|
356
|
+
kind="include_unavailable",
|
|
357
|
+
path=display_path,
|
|
358
|
+
message="Could not read include file.",
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
project = workspace.active_project
|
|
363
|
+
metrics = aggregate_project_metrics(
|
|
364
|
+
analyzed_units,
|
|
365
|
+
include_loc=include_loc,
|
|
366
|
+
include_metrics=include_metrics,
|
|
367
|
+
project_id=workspace.active_project_id,
|
|
368
|
+
project_name=project.name if project is not None else "",
|
|
369
|
+
)
|
|
370
|
+
if problems:
|
|
371
|
+
metrics = replace(
|
|
372
|
+
metrics,
|
|
373
|
+
problems=(
|
|
374
|
+
*metrics.problems,
|
|
375
|
+
*sorted(problems, key=lambda item: (item.path.casefold(), item.kind)),
|
|
376
|
+
),
|
|
377
|
+
)
|
|
378
|
+
return metrics
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def build_path_metrics(
|
|
382
|
+
root: str | Path,
|
|
383
|
+
source_files: Iterable[str | Path],
|
|
384
|
+
*,
|
|
385
|
+
defines: Iterable[str] = (),
|
|
386
|
+
include_paths: Iterable[str | Path] = (),
|
|
387
|
+
project_config: ProjectPathConfig | None = None,
|
|
388
|
+
project_name: str = "",
|
|
389
|
+
workers: int = 0,
|
|
390
|
+
on_progress: MetricProgressCallback | None = None,
|
|
391
|
+
) -> ProjectMetrics:
|
|
392
|
+
"""Analyze file paths without retaining the complete source corpus in RAM."""
|
|
393
|
+
|
|
394
|
+
root_path = Path(root).expanduser().resolve()
|
|
395
|
+
unique_paths: dict[str, Path] = {}
|
|
396
|
+
for value in source_files:
|
|
397
|
+
path = Path(value).expanduser()
|
|
398
|
+
if not path.is_absolute():
|
|
399
|
+
path = root_path / path
|
|
400
|
+
path = path.resolve()
|
|
401
|
+
if (
|
|
402
|
+
project_config is not None
|
|
403
|
+
and project_config.excludes_workspace_path(path)
|
|
404
|
+
):
|
|
405
|
+
continue
|
|
406
|
+
unique_paths.setdefault(str(path).replace("\\", "/").casefold(), path)
|
|
407
|
+
unit_paths = [
|
|
408
|
+
path for path in unique_paths.values() if path.suffix.casefold() in {".pas", ".dpr", ".dpk"}
|
|
409
|
+
]
|
|
410
|
+
include_files = [
|
|
411
|
+
path for path in unique_paths.values() if path.suffix.casefold() == ".inc"
|
|
412
|
+
]
|
|
413
|
+
source_bytes = 0
|
|
414
|
+
for path in unit_paths:
|
|
415
|
+
try:
|
|
416
|
+
source_bytes += path.stat().st_size
|
|
417
|
+
except OSError:
|
|
418
|
+
continue
|
|
419
|
+
parser_mode = (
|
|
420
|
+
ParserMode.TOLERANT
|
|
421
|
+
if len(unit_paths) >= _LARGE_PROJECT_UNIT_THRESHOLD
|
|
422
|
+
or source_bytes >= _LARGE_PROJECT_BYTE_THRESHOLD
|
|
423
|
+
else ParserMode.STRICT
|
|
424
|
+
)
|
|
425
|
+
define_values = tuple(defines)
|
|
426
|
+
include_path_values = tuple(str(Path(value)) for value in include_paths)
|
|
427
|
+
analyzed_units: list[UnitMetrics] = []
|
|
428
|
+
include_metrics: list[UnitMetrics] = []
|
|
429
|
+
problems: list[MetricProblem] = []
|
|
430
|
+
total_files = len(unit_paths) + len(include_files)
|
|
431
|
+
completed_files = 0
|
|
432
|
+
|
|
433
|
+
def consume_result(result: _MetricResult) -> None:
|
|
434
|
+
nonlocal completed_files
|
|
435
|
+
if result.metrics is not None:
|
|
436
|
+
analyzed_units.append(result.metrics)
|
|
437
|
+
if result.problem is not None:
|
|
438
|
+
problems.append(result.problem)
|
|
439
|
+
completed_files += 1
|
|
440
|
+
if on_progress is not None:
|
|
441
|
+
on_progress(completed_files, total_files, result.source_path)
|
|
442
|
+
|
|
443
|
+
_run_metric_tasks(
|
|
444
|
+
(
|
|
445
|
+
_MetricTask(
|
|
446
|
+
ordinal,
|
|
447
|
+
str(path),
|
|
448
|
+
_display_path(path, root_path),
|
|
449
|
+
"",
|
|
450
|
+
define_values,
|
|
451
|
+
include_path_values,
|
|
452
|
+
parser_mode,
|
|
453
|
+
project_config,
|
|
454
|
+
)
|
|
455
|
+
for ordinal, path in enumerate(unit_paths)
|
|
456
|
+
),
|
|
457
|
+
configured_workers=workers,
|
|
458
|
+
on_complete=consume_result,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
include_loc = 0
|
|
462
|
+
for path in include_files:
|
|
463
|
+
try:
|
|
464
|
+
source = read_source_text(path)
|
|
465
|
+
item = analyze_include(source, _display_path(path, root_path))
|
|
466
|
+
include_metrics.append(item)
|
|
467
|
+
include_loc += item.lines.total_lines
|
|
468
|
+
except (OSError, UnicodeError):
|
|
469
|
+
problems.append(
|
|
470
|
+
MetricProblem(
|
|
471
|
+
kind="include_unavailable",
|
|
472
|
+
path=_display_path(path, root_path),
|
|
473
|
+
message="Could not read include file.",
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
finally:
|
|
477
|
+
completed_files += 1
|
|
478
|
+
if on_progress is not None:
|
|
479
|
+
on_progress(completed_files, total_files, str(path))
|
|
480
|
+
if total_files == 0 and on_progress is not None:
|
|
481
|
+
on_progress(1, 1, "")
|
|
482
|
+
metrics = aggregate_project_metrics(
|
|
483
|
+
analyzed_units,
|
|
484
|
+
include_loc=include_loc,
|
|
485
|
+
include_metrics=include_metrics,
|
|
486
|
+
project_name=project_name,
|
|
487
|
+
)
|
|
488
|
+
if problems:
|
|
489
|
+
metrics = replace(
|
|
490
|
+
metrics,
|
|
491
|
+
problems=(
|
|
492
|
+
*metrics.problems,
|
|
493
|
+
*sorted(problems, key=lambda item: (item.path.casefold(), item.kind)),
|
|
494
|
+
),
|
|
495
|
+
)
|
|
496
|
+
return metrics
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def project_metric_item(metrics: ProjectMetrics) -> dict[str, object]:
|
|
500
|
+
return {"item_type": "project_metrics", **metrics.to_mapping()}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def unit_metric_item(metrics: UnitMetrics, *, detail: bool = False) -> dict[str, object]:
|
|
504
|
+
return {"item_type": "unit_metrics", **metrics.to_mapping(detail=detail)}
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _workspace_path(root: Path, value: str) -> Path:
|
|
508
|
+
path = Path(value).expanduser()
|
|
509
|
+
if not path.is_absolute():
|
|
510
|
+
path = root / path
|
|
511
|
+
return path.resolve()
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _display_path(path: Path, root: Path) -> str:
|
|
515
|
+
try:
|
|
516
|
+
return path.relative_to(root).as_posix()
|
|
517
|
+
except ValueError:
|
|
518
|
+
return path.as_posix()
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
__all__ = [
|
|
522
|
+
"build_path_metrics",
|
|
523
|
+
"build_workspace_metrics",
|
|
524
|
+
"project_metric_item",
|
|
525
|
+
"unit_metric_item",
|
|
526
|
+
]
|