An unreadable DESCRIPTION left RPackageConfig.truncated false, so a
qualifier naming the package it declares was classed external and its
call site dropped. The sibling hidesUndiscoveredPackage case already
treats an unreadable DESCRIPTION as unknown; do the same here and cover
it with a test.
Tighten three weak test assertions: the escaped-bracket export pattern
now asserts its exact match results, the S4 anchors test proves each
generic is defined exactly once, and the worker owner-absence test
fails on any defined ownerId, including an empty string.
extractS4Fields and getRTopLevelPropertyOwnerName named the owner of a
setClass/setRefClass slot from the first string-valued argument, so
setClass(contains = "Base", Class = "A", representation(x = "numeric"))
attached slot x to Base. Select the owner with isRNamingArgument(arg, 'Class'),
the same choice the scope query and definition hook make.
Both owner paths share one helper so the field metadata and the ownerNameHint
agree.
Each exportPattern matcher has its own work cap, but nothing bounded how many
patterns a repository may carry, so a NAMESPACE with 200 pathological patterns
cost about 77 s of analysis and one with tens of thousands of cheap patterns
cost minutes and gigabytes.
loadRPackageConfig now creates one SharedWorkBudget per call and a sub-budget
per package. Every matcher draws on its package's sub-budget and on the load's:
state visits spent matching (100M per load, 30M per package) and NFA states kept
by compiled patterns (500k per load, 100k per package). A pattern that does not
fit the state allowance is refused before its NFA is fully built, and the first
refusal closes the allowance so later patterns are refused without being parsed.
Once the matching budget is spent, refineRExportStatus and the R import resolver
stop calling the matchers. Refused and cut-short patterns take the existing
drop-and-warn path (export nothing), with at most 50 named per package and one
summary line for the rest. A hostile package can therefore not starve the
packages after it. Budgets count work units, so the outcome is deterministic.
Benign monorepos spend a negligible fraction of every budget.
The R scope query took the name of a setClass/setRefClass class and of a
source/library/require import from the call's first argument. A named first
argument was taken as the name (library(lib.loc = libO, pkgP) imported libO,
setClass(contains = "Base", Class = "A") declared Base), and a comment before
the first argument hid it (the import was lost, and a class declared after a
comment had no EXTENDS edge to VIRTUAL or to its base).
Capture every candidate argument and let the scope emitter keep only the one
that names the class or import: the argument spelled with the call's first
formal (Class, package, file), otherwise the first unnamed argument, with
comments and named arguments allowed before it. The selection is the one the
S4 definition captures already use, and reads each argument list once, so cost
stays linear in the argument count; a query-level prefix that skips leading
comments would be quadratic.
The parse-cache version bump already made for the unreleased S4 definition fix
covers the changed scope captures too, so no further bump is made.
The setClass/setRefClass, setGeneric and setMethod definition patterns matched
any string-valued argument of the call, so every extra string minted a node:
contains = "VIRTUAL" a Class VIRTUAL, valueClass = "numeric" a Function numeric,
and the class in setMethod("show", "Foo", ...) a Method Foo (with a
HAS_METHOD Foo -> Foo edge).
Keep only the argument that names the definition, chosen by the R provider's
shouldSkipDefinitionCapture hook: the argument spelled with the call's first
formal in any position (Class for setClass and setRefClass, name for
setGeneric, f for setMethod), otherwise the first unnamed argument, which
comments and named arguments may precede. A named formal stops a later unnamed
string from also being taken.
The choice is made in code because a query-level prefix that skips leading
comments and named arguments is quadratic in the argument count on every call
in the file. The code reads each argument list once, so cost stays linear, and
a bounded-time test guards it. The slots/fields/representation pattern is keyed
on named arguments only and is unchanged. The EXTENDS edge to VIRTUAL never
depended on the spurious node: scope resolution mints the single placeholder
Class itself.
SCHEMA_BUMP is incremented because the definitions are parse-time captures
replayed from the warm cache. Characterisation tests that pinned the spurious
Method and HAS_METHOD edge are corrected.
The npm-dependency cohort was selected with /^tree-sitter-/ on the full
package name, which silently dropped @eagleoutice/tree-sitter-r. Select by
package basename and assert the enumerated set so the filter cannot regress.
The readiness report and the --assert-current ABI gate never covered the R
grammar, whose latest release declares peer tree-sitter ^0.21.0 (a blocker
for the 0.25 target). Adding the row also needs three fixes for the scoped
package name: the pinned-version lookup matched only names starting with
"tree-sitter-", and the change-detection regex (workflow and test mirror)
could not capture a scoped row. Test counts updated; new tests cover the R
row, the pin, the gate, and mirror-versus-workflow drift.
The S4/R5 contains= (single and c()), R6 inherit= (namespaced and bare)
and (comment) patterns capture @heritage*, @comment, which no code
reads: such a match has no @name and no @definition.*, so the worker
discards it. Inheritance edges come from the R scope query's
@reference.inherits.
Graph nodes, relationships, properties, resolution outcomes and the raw
worker output are byte-identical over the r-* fixtures and probe repos
before and after, so no re-analyse and no SCHEMA_BUMP are needed.
Also correct the R scope query header, which described the legacy table
as having proved the heritage shapes.
The tag name was never checked against the function's formals and the type was
bound in the scope that contains the assignment, so a documented parameter
typed that name in every function of the file. The type is now bound per
formal, inside the function that owns it, and only for a formal of the
function the roxygen block documents.
Also pin the line-start anchor with a case that fails without it, and a tag
name that merely starts with `param`.
Run the prose case against both an R6 and an S4 definition of both `Data` and
`Config`, so each class-definition form is exercised with each prose word.