Commit graph

1138 commits

Author SHA1 Message Date
Mick Cooney
a7ec43dbc6 refactor(r): remove five dead patterns from R_QUERIES
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.
2026-10-01 13:37:58 +01:00
Mick Cooney
b03684cbde fix(r): scope a roxygen @param type to a formal of the documented function
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`.
2026-10-01 13:37:58 +01:00
Mick Cooney
165cf12508 fix(r): bind a roxygen @param type only from an anchored form naming a local class
A @param description is free prose, but the first word of any capitalised
description was bound as the parameter's type (`@param df Data frame ...`
gave `df: Data`). A type is now bound only when the description is the type
alone or starts with it in braces, and the type names an R6, S4 or RefClass
class defined in the same file. Tags are read only at the start of a roxygen
line and a description runs to the next tag.

Remove the unwired extractReturnType, which took the first word of @return the
same way.
2026-10-01 13:37:58 +01:00
Mick Cooney
f5e0ea374d refactor(r): keep packageDirs for the locality fallback only
The packageDirs loop inside rOwningPackageDir could only matter when one
package directory sits inside another package's R/ directory, and no test
could observe it. NAMESPACE ownership now reads packages and NAMESPACE
directories only; rFileBelongsToDiscoveredPackage adds packageDirs for the
<name>/R/ locality fallback, where the same-name-copy test pins it. A new
test checks that discovery populates packageDirs.
2026-10-01 13:37:58 +01:00
Mick Cooney
c8823b566b fix(r): ignore the <name>/R/ locality fallback for files a discovered package owns
rQualifierLocality treated a qualifier as a local package when any parsed
file sat under a directory of that name followed by R/, even when discovery
had attributed the directory to a package with a different Package: name.
The directory name is now evidence only for files no discovered package owns.
2026-10-01 13:37:58 +01:00
Mick Cooney
2d3335927b fix(r): scope NAMESPACE export refinement to each package's R/ directory
refineRExportStatus matched a file against the package directory, so
functions in tests/, vignettes/ and inst/ were marked not exported although
R never loads them into the namespace, and a nested package without a
NAMESPACE inherited the enclosing package's. Ownership now comes from the
nearest discovered package whose R/ directory holds the file
(rOwningPackageDir), over every directory discovery read a DESCRIPTION in.

library() of a root-level package resolved to nothing because its directory
'' is falsy in resolveRImportInternal; it now resolves to the package's R/
files like a nested package.
2026-10-01 13:37:58 +01:00
Mick Cooney
98ac287601 fix(r): skip renv, packrat, revdep and .Rcheck trees in package discovery
Installed libraries and check output hold one DESCRIPTION per third-party
package. Discovery treated them as repository content: a renv project
library below the depth limit flagged discovery as truncated, which made
every pkg::fn() qualifier unknown, and a shallow copy became a local
package with no parsed files.

The main walk and the hidden-package scan now share one predicate that
skips a directory whose name is exactly renv, packrat or revdep, or ends in
.Rcheck after at least one character. Look-alike names are still searched.
2026-10-01 13:37:58 +01:00
Mick Cooney
fb6fbff036 fix(r): rank duplicate package directories by whole path
Among same-depth directories declaring one package name, the winner was
decided by breadth-first walk order, which compares path segment by
segment (a/x before a-b/x). Compare the whole relative path by code unit
instead (a-b/x before a/x), so the winner no longer depends on how the
walk happens to meet the directories, and update the comments to say so.
2026-10-01 13:37:58 +01:00
Mick Cooney
6f4f85bcc3 fix(r): make package discovery independent of readdir order
Package discovery used the filesystem's directory listing order, so the
200-directory cap visited different directories, and a package name
declared in several directories resolved to a different directory, on
different filesystems. Read every directory in code-unit name order and
keep the first package seen for a name: the shallowest directory, then the
path that sorts first. A root-level package is no longer replaced by a
nested copy of the same name.
2026-10-01 13:37:58 +01:00
Mick Cooney
12ba50217a fix(r): keep large exportPattern alternations and warn when a pattern is dropped
The linear-time exportPattern matcher refused anything over 1,024 states or
4,096 characters, so a legitimate `^(f1|...|f80)$` or `^[[:alpha:]]{1,2000}$`
compiled to nothing and every name in the package read as unexported, with no
diagnostic.

Raise the caps to 16,384 states and characters, and bound the hostile case by a
per-matcher work budget instead: a matcher counts the states it visits and, once
40 million are spent (about 0.4 s), answers false for names it has not decided
and reports itself exhausted. Alternations of literals are built as a trie so
alternatives sharing a prefix share states, the start closure is looked up by
code unit, closures are iterative, and verdicts are memoised per distinct name.

Every non-empty exportPattern that is dropped, and every matcher that runs out
of budget, now produces one warning per pattern per package naming the package
directory, the first 60 characters of the pattern and the reason. The dropped
patterns are also kept on RNamespaceInfo.droppedExportPatterns.
2026-10-01 13:37:58 +01:00
Mick Cooney
84d2a8be4a fix(r): match exportPattern in linear time
exportPattern() text comes from the analysed repository and was compiled
to a backtracking RegExp, so a pattern such as ^(a+)+$ took 1.6 s on a
27-character name and minutes on a slightly longer one, hanging analyze.
R itself evaluates these with the non-backtracking TRE.

Compile the pattern to a Thompson NFA and simulate it instead, so a match
costs O(name length x NFA size) for any pattern. Constructs with no
linear-time algorithm (back-references, look-around) and patterns that
expand beyond a fixed state cap are dropped like an uncompilable pattern.
Verdicts for ordinary patterns are unchanged, checked against the
previous RegExp on a generated corpus.
2026-10-01 13:37:58 +01:00
Mick Cooney
c885001568 chore(r): remove remaining label pointers from code and tests
Rewrite comment and describe-title text that pointed at labels defined
nowhere in the repository and history-narrative wording that referred to
earlier work, stating each rule directly. No behaviour change; test counts
are unchanged.
2026-10-01 13:37:58 +01:00
Mick Cooney
ce5041a64c chore(r): rewrite planning-note pointers in code and tests as self-contained text
Rewrite references to planning notes (section numbers, item labels, review
rounds, scenario/unit numbering) as self-contained statements of the rule or
trade-off. Comment, fixture comment and test-description text only; no
behaviour change.
2026-10-01 13:37:58 +01:00
Mick Cooney
3dbc46f00c chore(r): make comments, test names and docs self-contained
Replace issue-number references in comments, test names and descriptions with
self-contained wording, drop merge-coordination notes from the SCHEMA_BUMP
history, and rename a fixture file to qualified_use.R. Comment,
description and file-name changes only.
2026-10-01 13:37:58 +01:00
Mick Cooney
d18d24e408 style(r): format namespace-imports and parse-worker with the pinned prettier 2026-10-01 13:37:58 +01:00
Mick Cooney
4bdacd539e fix(r): treat a completed package scan with zero packages as proving pkg:: calls external
loadRPackageConfig returned null when discovery found no package, and
rQualifierLocality maps a null config to unknown, so in a repo with no
DESCRIPTION anywhere dplyr::filter() still bound to a local filter through
the name guess. Return an empty config instead: its truncated flag now
distinguishes a complete scan (external) from one that may have missed a
package (unknown, name guess kept).
2026-10-01 13:37:58 +01:00
Mick Cooney
d2b3c2497d docs(r): document qualifier-aware call resolution and its limits 2026-10-01 13:37:58 +01:00
Mick Cooney
463626572b fix(r): flag package discovery as truncated only when a skipped subtree hides an undiscovered package
Discovery set RPackageConfig.truncated whenever it skipped any directory
(depth-3 limit) or hit the 200-directory cap, so nearly every real repo
(tests/testthat/fixtures, inst/extdata, ...) counted as truncated. Qualified
calls to outside packages were then classified unknown and kept, leaving the
dplyr::filter() bound to a local filter() case unfixed.

Skipped directories and the cap leftover queue are now collected and scanned
by hidesUndiscoveredPackage (iterative DFS, global 5000-directory budget,
skipping node_modules, .git, .Rproj.user). truncated is set only when that
scan finds a DESCRIPTION whose Package: name is not already discovered,
exceeds the budget, or hits an unreadable path; an unreadable directory in
the main walk still truncates.
2026-10-01 13:37:58 +01:00
Mick Cooney
1b53efb12d feat(r): resolve pkg::fn to local package definitions and veto contradicting name guesses
The scope query now captures the qualifier of `pkg::fn()` / `pkg:::fn()`; use it.

- qualified-call.ts (new): parseRQualifier, a tri-state rQualifierLocality
  (local / external / unknown) and populateRQualifiedCalls, wired after the
  NAMESPACE import synthesis in R's populateWorkspaceReferences.
  * external package (discovery complete): the site is dropped, so no tier
    can guess an edge for it;
  * local package with exactly one top-level definition of the name: bound
    precisely (0.85) through resolveQualifiedFreeCall, keyed by file + start
    position;
  * local package with two or more definitions: the site is dropped;
  * local package with none (re-export) or unknown locality: unchanged.
- package-config.ts: RPackageConfig.truncated is set when the directory cap,
  the depth limit or an unreadable directory left discovery incomplete, so a
  package is only called external when the scan was complete.
- isRGlobalNameFallbackPlausible: a candidate inside the named local package
  is allowed, one elsewhere is vetoed when the package defines the name.
- Flip the tests that pinned the pre-fix name-only results and drop the
  temporary pins and it.fails markers of the step-1 tests.

No shared-core changes.
2026-10-01 13:37:58 +01:00
Mick Cooney
ed9d602e80 feat(r): capture the pkg:: qualifier on namespaced calls
Capture the whole namespace_operator of pkg::fn() and pkg:::fn() calls as
@reference.qualified-name so the qualifier reaches site.rawQualifiedName and
survives the parse cache. On its own this changes no graph output: no R
consumer reads the qualifier yet.

SCHEMA_BUMP is incremented because warm ParsedFiles lack the new capture.
2026-10-01 13:37:58 +01:00
Mick Cooney
d902526f6b refactor(ingestion): stop writing the R-only ownerId property onto every language's definition nodes
The node-property ownerId spread in the parse worker was added with R
support and is never read for non-R languages; upstream's worker never
wrote it onto definition nodes. R never had it either (its owner is
attached post-parse). Symbol ownerId and HAS_METHOD/HAS_PROPERTY edges
are unchanged.
2026-10-01 13:37:58 +01:00
Mick Cooney
c96fc05c1b refactor(r): compute ownerNameHint in definitionPropertiesExtractor; drop dead owner plumbing
Move R's deferred-owner hint out of the shared parse worker into R's
definitionPropertiesExtractor (rDefinitionProperties), and remove the
worker-side hint block, the node/symbol ownerNameHint spreads,
ParsedSymbol.ownerNameHint and the R extractor imports. Delete the dead
hasRoxygenExport branch from rExportChecker (both branches returned true).
2026-10-01 13:37:58 +01:00
Mick Cooney
c2fcdd1bc5 refactor(ingestion): add LanguageProvider.resolveMemberOwnerNode and move R's owner-node lookup behind it
Add an optional resolveMemberOwnerNode hook consulted by the worker's
enclosing-owner helper after the container walk and resolveFileTypeOwner.
R implements it via rResolveMemberOwnerNode, replacing the two
language === R branches in parse-worker.
2026-10-01 13:37:58 +01:00
Mick Cooney
1e7985db60 refactor(r): run R post-parse through LanguageProvider.postParse
Move r-post-parse.ts into languages/r/post-parse.ts, expose rPostParse and
wire it on the R provider only. The parse phase drops its R-specific block
and relies on the generic runPostParseHooks call, so a repo with no R files
no longer reads DESCRIPTION/NAMESPACE. Flip the gating test from it.fails to it.
2026-10-01 13:37:58 +01:00
Mick Cooney
ad47dafd6e refactor(r): move RPackageConfig and loadRPackageConfig into languages/r/package-config 2026-10-01 13:37:58 +01:00
Mick Cooney
e05b0118bf feat(ingestion): add LanguageProvider.postParse and a per-provider runner
Add an optional LanguageProvider.postParse(ctx: PostParseContext) hook, the
PostParseContext type and runPostParseHooks, a pure runner beside
prepareRouteConstantsByProvider that takes a provider-lookup callback (no
registry import). It runs hooks sequentially in language-id order, awaits each
and does not isolate throws, because the hooks mutate the shared graph/model.

runChunkedParseAndResolve computes the languages present among the parseable
scanned files and calls the runner immediately after the existing R block
(same statement position: after every chunk is merged and the export-map
enrichment, before scope resolution). No provider defines the hook yet, so
this is a no-op; the R block is removed in a later step.
2026-10-01 13:37:58 +01:00
Mick Cooney
b7728e72b5 fix(r): keep S3 method names from colliding with bare names in simple-name indexes
The shared simple-name indexes key every def by the text after the last '.'
of its qualifiedName, so an S3 method `print.foo` is indexed under `foo`
next to a bare `foo`. Two measured defects followed:

- `library(prov)` bound `foo` to `print.foo` (a wrong 0.85 import-resolved
  edge) when the S3 method was declared before the bare def;
- a same-package cross-file bare call `foo(v)` saw two candidates under
  `foo` and got no CALLS edge at all.

R-only fix, no shared-core edit, no SCHEMA_BUMP (both hooks run at resolve
time over cached ParsedFiles):

- rScopeResolver.isCallableVisibleFromCaller refuses dotted candidates: a
  bare R call can never invoke `print.foo`.
- expandRWildcardNames drops a bare name whose file also defines a dotted
  top-level def with that tail (order-blind, like the importFrom guard).

Characterisation tests deliberately updated to the new behaviour (r.test.ts,
R NAMESPACE importFrom fixture):
- print.tidy before tidy / tally before print.tally: was no edge, now a
  0.5 global-name-fallback edge to the bare def;
- bare call to a name that only an R6 method carries: was a 0.5 guess to
  the method, now no edge (a bare call cannot reach an R6 member).

Residual: dotted names bind only through qualified-name lookup (0.35),
never import-resolved; a bare name sharing a tail with an S3 method in the
same file resolves at 0.5 (fallback), not 0.85; backticked names unchanged.
2026-10-01 13:37:57 +01:00
Mick Cooney
7c383a31f8 fix(r): resynchronise NAMESPACE directive scanning after a malformed directive
Since the shared scanner landed, a garbled or unbalanced
directive stopped parsing the rest of the NAMESPACE file, silently dropping
every later export/importFrom directive (the old line-based export parser
resumed at the next line).

The scanner now discards the malformed directive and resumes at the next line
that begins, at column 0, with `identifier(`. To keep that safe:
- quoted strings no longer span lines, so a stray quote cannot pair with a
  later one and swallow the directives in between;
- a column-0 `identifier(` line ends any argument list still open, which also
  bounds each failed scan to the next directive rather than the end of file;
- the resume point is always strictly after the failing directive's line, so
  scanning always makes forward progress.

An indented directive following a malformed one is dropped with it. Both
parseRNamespaceExports and parseRNamespaceImportFrom benefit. No existing test
pinned stop-on-error behaviour; all pass unchanged.
2026-10-01 13:37:57 +01:00
Mick Cooney
c347764226 fix(r): apply NAMESPACE export status to root-level packages
refineRExportStatus never refined a package whose DESCRIPTION/NAMESPACE sit
at the repository root (package dir ''). Two defects combined:

- the containment test used startsWith('' + '/'), i.e. '/', which no
  repo-relative path matches;
- the result of pkgDirs.find() was guarded with `!pkgDir`, which treats the
  valid root key '' as "no package".

Treat '' as containing every path (it sorts last, so nested packages still
win) and compare the find() result against undefined. Adds a synthetic
root-package fixture, an integration test and unit tests covering export(),
exportPattern(), S3method(), unexported helpers, no-NAMESPACE, and the
unchanged nested-package behaviour.
2026-10-01 13:37:57 +01:00
Mick Cooney
3655e31b28 fix(r): parse multi-line NAMESPACE directives and unescape exportPattern strings
loadRPackageConfig read export(), exportClasses(), exportMethods(),
S3method() and exportPattern() line by line, so any wrapped directive was
silently skipped. Read them from the whole NAMESPACE text with the tokeniser
already used for importFrom(): the tokeniser is split into a shared
scanRNamespaceDirectives, and parseRNamespaceExports is a second consumer.
Wrapped, quoted, backticked, commented and CRLF directives now give the same
result as their single-line form; S3method() accepts its optional third
argument and exportPattern() accepts several patterns.

exportPattern() strings are now R-unescaped before compilation, so the R
string "\\." becomes the regex \. rather than \\. (unescapeRString handles
\\, \", \', \n, \t, \r, \a, \b, \f, \v and \x/\u/\U code points).

loadRPackageConfig changes only at the call site; no parse-cache output
changes.
2026-10-01 13:37:57 +01:00
Mick Cooney
6c0183d33b fix(r): translate POSIX character classes in exportPattern()
exportPattern() arguments are POSIX (TRE) regexes, but loadRPackageConfig
compiled them with new RegExp(), so classes such as [[:alpha:]] were read as
a JS character set and never matched. The RStudio default
exportPattern("^[[:alpha:]]+") therefore exported nothing, and named-import
export checks judged every name non-exported.

Add compileRExportPattern (languages/r/export-pattern.ts): rewrites
[:alpha:], [:alnum:], [:digit:], [:upper:], [:lower:], [:punct:],
[:space:] (plus blank, cntrl, print, graph, xdigit) inside bracket
expressions to ASCII JS equivalents, handles negation and mixed ranges, and
returns null for unknown classes or unterminated brackets. Patterns without
"[:" compile verbatim, so existing behaviour is unchanged. Compilation still
happens once per pattern in loadRPackageConfig.

Deliberate test change: the "POSIX exportPattern characterisation" in
r-namespace-imports.test.ts pinned the wrong behaviour (a JS-compiled POSIX
pattern refuses every name). It now asserts the corrected behaviour, plus a
non-matching control.
2026-10-01 13:37:57 +01:00
Mick Cooney
0c16954425 fix(r): clear eslint errors in changed files 2026-10-01 13:37:57 +01:00
Mick Cooney
b0fc635475 style(r): format changed files with prettier 2026-10-01 13:37:57 +01:00
Mick Cooney
c415f35812 feat(r): veto global-name fallback guesses contradicted by importFrom
Refuse a unique-name global fallback edge when the caller package NAMESPACE
imports the name (last importFrom entry wins) from a package other than the
one the guess landed in and other than the caller's own package. Callers and
candidates outside a package R/ directory, local providers that re-export the
name, self-imports and undecidable configs keep the baseline edge. The
pkg::name() and import() limitations are pinned by characterisation tests.
2026-10-01 13:37:57 +01:00
Mick Cooney
49db332589 feat(r): bind NAMESPACE importFrom names to local package definitions
Synthesise a `named` import for every NAMESPACE importFrom(pkg, name) whose
pkg is a local package, into each R/ file of the importing package
(populateRNamespaceImports, wired via populateWorkspaceReferences with
postExtractSourceTextPolicy 'uncached-files'), and resolve it in
resolveRImportTarget's named branch.

Rules: last entry wins per name over all entries; a self-import, an
external last entry, and a name the importing package defines at top level
(own namespace masks) synthesise nothing. The named branch builds no
workspace file index, returns null for external or unknown packages, dotted
names (finalize keys defs by the text after the last dot), providers whose
NAMESPACE does not export the name (backticks stripped), names with no
top-level definition, and a defining file that also holds a dotted def
whose tail equals the name. Top-level definitions are read from the Module
scope's hoisted bindings, not ownedDefs/localDefs.

Known limitations, pinned by tests: pkg::name() qualifiers are discarded
at this point, a nested same-named function can still win the binding, and
POSIX-class exportPattern() never matches in a JS RegExp.
2026-10-01 13:37:57 +01:00
Mick Cooney
83571b66a2 feat(r): bind named imports to top-level definitions only
Set namedImportsBindTopLevelOnly on rScopeResolver so a named import can
only bind a module-level definition of the target file, never an R6/R5
member with the same simple name. Groundwork for NAMESPACE importFrom()
binding; r.test.ts (127) is unchanged by the flag alone.
2026-10-01 13:37:57 +01:00
Mick Cooney
a437b04c06 feat(r): parse NAMESPACE importFrom() directives 2026-10-01 13:37:57 +01:00
Mick Cooney
31e006fb5e fix(r): anchor function scopes on assignment and method-argument nodes
Every R call site's CALLS source is now the callable that lexically contains
it (named function, nested named function, R6/R5 method), or the File node when
no named callable encloses it; a call in a class body outside any method (an
R6 field default, a setClass validity body) is sourced from the Class node via
the unchanged resolveCallerGraphId class-anchor fallback.

Cause: R's @scope.function anchored only on the function_definition, while
@declaration.function / @declaration.method anchor on the enclosing assignment /
named argument. The def was therefore owned by the module / outer-function /
Class scope, and resolveCallerGraphId credited every call in a body to the FIRST
callable owned by that scope (false inner -> inner self-loops; module-level
calls credited to a function instead of File).

Fix (languages/r/query.ts only; no shared pipeline change, no @declaration.*
change so def ids and resolveDefGraphId reconciliation are untouched):
- (binary_operator lhs: (identifier) rhs: (function_definition)) @scope.function,
  directly beside the @declaration.function pattern it must track.
- (argument name: (identifier) value: (function_definition)) @scope.function,
  directly beside the @declaration.method patterns; generic, so it cannot drift
  from the three R6/R5 nesting patterns, and inert for non-method arguments.
- Both carry a "must stay in lockstep" comment; the blanket function_definition
  scope stays (anonymous functions); the query header rationale is rewritten.

Cache: SCHEMA_BUMP is incremented (ledger comment added, pin test updated). Warm
ParsedFiles and durable stores hold the pre-fix scope tree.

Tests: removes every it.fails wrapper from the tests added in the previous commit
(18 unit, 36 integration) and deletes the pins of the old, defective behaviour
(5 unit, 4 integration);
they now pass as ordinary tests. No other assertion was changed.

Verification (built dist): scope-function-anchors 42/42, r.test.ts 97/97
(39 existing + 58 new), incremental-parse-cache, call-arguments-r,
external-import-conformance, import-target-index-reuse.contract, the three
grammar-literal gate files, tsc --noEmit clean.
2026-10-01 13:37:57 +01:00
Mick Cooney
d2be5fd3d5 feat(r): resolve the S4 VIRTUAL heritage sentinel
setClass(x, contains = "VIRTUAL", ...) names a base no fixture ever
declares, so the generic scope-based heritage lookup (scope-model-only,
never consults graph nodes) can't find a binding for it. Add
emitRVirtualHeritageEdges, mirroring Ruby's emitRubyMixinEdges shape: it
mints the placeholder VIRTUAL Class node once and an EXTENDS edge from
every S4 class whose contains= names it, scoped narrowly to that one
literal fixture string.

All 39/39 r.test.ts scenarios now pass.
2026-10-01 13:37:57 +01:00
Mick Cooney
658e5ce840 feat(r): wire R onto the scope-resolution pipeline
Add emitRScopeCaptures/interpretRImport/interpretRTypeBinding and a new
rScopeResolver, modeled on Zig's/Ruby's shape, and register it in
SCOPE_RESOLVERS. Captures cover scopes (function/R6-class/S4-class, the
class scope spanning the whole R6Class()/setClass() call so inherit=/
contains= sites resolve inside it), declarations (function, class without
a qualified_name so the bare-name fallback matches the legacy node,
R6/R5 methods), imports (source() as side-effect, library()/require() as
wildcard per the unfiltered-by-NAMESPACE policy), the R6 constructor
type-binding (Type$new(...)), and heritage references (R6 inherit=, S4
contains=/contains=c()), which resolve via the generic
preEmitInheritanceEdges mechanism directly.

38/39 r.test.ts scenarios pass; only the VIRTUAL sentinel (no
setClass("VIRTUAL", ...) exists anywhere) remains, requiring a custom
emitHeritageEdges hook.

Adds R's entries to the two registry-census unit tests
(import-target-index-reuse.contract.test.ts,
external-import-conformance.test.ts) that assert every SCOPE_RESOLVERS
key has a fixture/case.
2026-10-01 13:37:57 +01:00
Mick Cooney
b9a4ed619f feat(r): add resolveImportTarget adapter for R import resolution
Wrap resolveRImportInternal in a ScopeResolver.resolveImportTarget-shaped
adapter (resolveRImportTarget), memoized on the allFilePaths Set identity
via getWorkspaceFileIndex (Ruby/PHP/C# pattern) instead of materializing
fresh arrays per import.

Guard cross-language false-positive resolution: EXTENSIONS has no .R/.r
entry, so a library()/require() package name not found in the R package
config would otherwise fall through to the generic suffix fallback and
suffix-match an unrelated same-named file in another language. The guard
is keyed on context.parsedImport.kind === 'wildcard' (set by
interpretRImport, added in a follow-up commit) so it is scoped to
library()/require() only; source()'s own suffix-based file-path fallback
is unaffected.

Widens resolveRImportInternal's array params to readonly string[] (no
behavioural change — the function only reads/indexes them) so the
adapter can pass the memoized index's arrays through without a
defensive copy.
2026-10-01 13:37:57 +01:00
Mick Cooney
b63f522766 fix(r): precompile exportPattern regexes and tidy R provider wiring
- Precompile NAMESPACE exportPattern() regexes once in loadRPackageConfig
  instead of recompiling them per node in refineRExportStatus; patterns
  that fail to compile are dropped, as before.
- Make attachDeferredROwners skip non-R nodes immediately in its
  full-graph traversal.
- Classify R as experimental in the language classification table,
  alongside zig, vue, cobol and objective-c.
- Drop the unused importSemantics: 'wildcard-leaf' field from the R
  provider.
- Add the required column field to the R6 and RefClass method-extraction
  sites (#2936).
- Use the local model instead of ctx.model when attaching deferred R
  owners in parse-impl.ts, where ctx is not in scope.
2026-10-01 13:37:57 +01:00
taiseii
5250ce2efa feat(r): add R language support with defineLanguage() pattern
Adds full R language support (39 integration tests + 14 unit tests passing).
R classes (S4/R5/R6) are expressed as function calls rather than
tree-sitter class syntax, so the worker can't always find enclosing
classes during AST walks. A new `r-post-parse.ts` resolves deferred
owner hints against the TypeRegistry and explicitly registers methods/
properties into MethodRegistry/FieldRegistry after parsing completes.

Changes:
- gitnexus-shared: add R enum + .r extension + Prism mapping
- languages/r.ts: defineLanguage() with wildcard-leaf imports, R6/R5/S4
  field+method extractors, roxygen2 type extraction, NAMESPACE-aware
  exports
- tree-sitter-queries: R_QUERIES for functions, classes, methods, fields,
  imports, heritage, roxygen2 comments (+ 246 lines)
- language-config: RPackageConfig + loadRPackageConfig() scanning
  DESCRIPTION/NAMESPACE for exports + exportPattern + S3method
- import-resolvers/types: add rPackageConfig to ImportConfigs
- parse-worker + parsing-processor: R ownerNameHint capture for
  setMethod("fn", "ClassName") + findRFieldOwnerNode fallback
- r-post-parse: attachDeferredROwners resolves ownerNameHint via
  TypeRegistry, mutates SymbolDefinition + registers into
  Method/FieldRegistry + emits HAS_METHOD/HAS_PROPERTY edges.
  refineRExportStatus applies NAMESPACE filtering per package.
- Integration tests cover <<- super-assignment, R6 private/active,
  S4 multi-parent EXTENDS, bare R6Class(), require(), dot-names,
  exportClasses/exportMethods, default-public without NAMESPACE,
  R6 field type inference from defaults, + heritage fix for S4
  contains= queries.
2026-10-01 13:37:57 +01:00
Gergő Magyar
e42122a0f6
feat(go): index gin/echo routes and report them in impact (#3402) (#3417) 2026-10-01 10:22:45 +01:00
azizur100389
acb65b95b6
fix(fastapi): propagate package router mount prefixes (#3408)
Some checks failed
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Gitleaks / gitleaks (push) Has been cancelled
Publish / Classify release event (push) Has been cancelled
Scorecard / Scorecard analysis (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-cli) (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-web) (push) Has been cancelled
Publish / RC guard (marker + release-PR skip) (push) Has been cancelled
Publish / ci (push) Has been cancelled
Publish / Publish to npm (push) Has been cancelled
Publish / Build & Push RC Docker images (push) Has been cancelled
* fix(fastapi): carry package router mount prefixes to child routes

* fix(fastapi): address review feedback on nested router prefixes (#3408)

- Skip unprefixed includes in the parse-impl legacy loop so a bare
  include_router in another file no longer shadows the real prefix.
- Union exact-file prefixes with legacy long/short prefixes via a shared
  mergeMountPrefixes helper in both ingestion and the group extractor.
- Join the parent APIRouter(prefix=...) between the mount prefix and the
  child include prefix.
- Resolve the group layer over every repo path (empty files included) so
  absolute-import ambiguity matches ingestion.
- Memoize (file, prefix) frames so diamond-shaped include graphs stay
  linear; drop the stack.pop() non-null assertion.
- Accept extra keyword arguments and a trailing comma in unprefixed
  include_router calls without double-firing on prefix= calls.
- Document that pass-through is limited to a host named `router`.
- Bump parse-cache SCHEMA_BUMP to 123 for the new capture fields.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(fastapi): seed prefix propagation from bare router mounts (#3408)

- A router mounted without a prefix now seeds traversal with an empty
  prefix (only when no prefixed mount targets the same file), so its own
  APIRouter(prefix=...) reaches unprefixed children on both surfaces.
- An all-empty chain records nothing and leaves the child on its legacy
  fallback.
- The bare-mount integration test no longer asserts that the test app's
  unprefixed mount is absent; it pins only that the real prefix survives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(fastapi): capture include prefixes after nested-call arguments (#3408)

- Let the Shape A/B and unprefixed include_router patterns step over one
  level of nested calls such as dependencies=[Depends(auth)], so a
  prefix= written after them is captured by the worker (the group
  layer's tree-sitter patterns already handled this shape).
- Replace the unit test that pinned the dropped prefix with one that
  pins the captured prefixes and the unprefixed Depends-only edge; add a
  group-layer parity test.
- Correct the diamond test comment to 2^39 root-to-leaf paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 14:49:44 +01:00
Gergő Magyar
aa0f41e853
fix(python): model restoring helper calls in decorator identity (#3415)
* fix(python): model restoring helper calls in decorator identity (#3414)

A bare module-level call to a same-file helper whose every global
binding of a descriptor name is an unconditional del or builtins import
now restores the builtin at the call site, matching CPython. A nonlocal
rebind nested in the enclosing function now shadows an owned builtins
import, closing a false builtin. Unprovable call orders stay fail-closed
and are pinned against CPython 3.11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(review): apply review findings

Close three false-builtin paths the review found: a match-pattern
capture now counts as a binding (at any scope, including inside a
restoring helper), a call before the helper's def no longer counts as a
restore, and the helper-name uniqueness check sees match captures.
Pin the helper rejections (conditional restore, async, early and nested
return, wildcard import) against CPython 3.11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cache): bump parse-cache schema to v122 for #3414

Python decorator identity verdicts changed, so warm v121 ParsedFiles
would replay stale receiver bindings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): require an argument-free call to a helper with no required parameters

A call that fails to bind the helper's parameters raises TypeError
before the body runs, so it proves no restore. Keep such calls
fail-closed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): count only real captures and module bindings for decorator identity

Match value patterns, class names and keyword keys read a name rather
than capture it, so they no longer shadow a builtin descriptor. A local
of the same name as a restoring helper no longer disqualifies the
module-level helper; only a module binding or a global rebind does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(python): state the fail-closed contract of the descriptor identity table

`false` means the resolver does not prove the builtin, not that CPython
shadows it. Cases where CPython keeps the builtin but the resolver fails
closed carry a comment saying so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 12:37:27 +01:00
Gergő Magyar
821ffb2fcb
fix(python): resolve decorator identity like CPython (#3411)
* fix(python): resolve decorator identity like CPython

Decorator identity was decided by two different predicates. One read
the raw decorator text, so a trailing comment such as
`@staticmethod  # type: ignore` hid the builtin and turned an explicit
first parameter into a fabricated receiver. The other trusted any
`staticmethod` spelling, including a module-level rebinding and a
`staticmethod(classmethod(f))` stack, which CPython cannot call.

Read the decorator expression node only, and recognize a bare builtin
descriptor only when the file does not rebind that name. Publish subtype
capacity only for a plain function or a single builtin staticmethod or
classmethod wrapper. Drop a no-op coverage guard, move the implicit
classmethod comment next to the code it describes, and bump the parse
cache to v121.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): shadow builtin descriptors only by visible bindings

The whole-file identifier scan counted plain reads (`staticmethod(f)`),
`from builtins import staticmethod`, and bindings that run after the
decorator as rebindings. CPython evaluates a class-body decorator with
LOAD_NAME when the `def` runs, so none of those change which object the
decorator names. Methods decorated with the real builtin lost their
subtype call shape, and static methods lost their first parameter in
arity metadata.

Move decorator identity into builtin-descriptors.ts and count only
binding occurrences (assignment and loop targets, walrus, def/class,
parameters, import aliases, except/with/match captures, del, type
parameters, and wildcard imports) that are visible where the decorator
runs: the class body or module before the definition, a repeating
enclosing loop, any binding in an enclosing function, and any
global/nonlocal rebind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): model global and del like CPython's symbol table

`global x` and `nonlocal x` bind nothing; they redirect the declaring
function's own bindings of `x` to an outer scope. A bare declaration
was treated as an unconditional rebinding, so `@staticmethod` anywhere
in the file lost builtin recognition.

A module- or class-level `del` restores the outer lookup rather than
binding the name. Treat an unconditional `del` that runs after a
binding and before the decorator as undoing that binding. A `del`
inside control flow may not run, and a `del` inside a function still
makes the name local there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): order global rebinds and honor builtins re-exports

A function that declares `global staticmethod` and assigns it rebinds
the module name only when called, and it cannot be called before the
top-level statement that defines it runs. Treat such a rebind as
visible only when that statement precedes the decorator, or when the
decorator sits in a deferred class body. `nonlocal` rebinds stay
visible anywhere in the enclosing function.

`from builtins import staticmethod as staticmethod` binds the builtin
to its own name, so it no longer counts as shadowing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(python): scope the descriptor-identity CPython claim

The wildcard-import case expects the fail-closed resolver verdict, not a
CPython outcome, because the imported module's exports are unknown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): resolve descriptor names as LOAD_NAME does

Model each binding by its effect on the namespace (Language Reference
4.2.1): an import that binds the `builtins` object itself (plain, aliased
to the same name, or `from builtins import *`) restores the builtin, a
module- or class-level `del` unbinds so lookup falls through, and any
other binding shadows. Resolve the decorator like LOAD_NAME (4.2.2):
the class namespace, then module globals, then builtins, each as it
stands when the `def` runs.

A restoring effect counts only as an unconditional simple statement that
runs before the decorator, so an import or `del` under `if`/`try` or a
loop stays fail-closed. A helper's `global` delete depends on whether
the helper is called, which the resolver does not model, so it keeps the
override.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(python): simplify decorator descriptor lookup

Derive the descriptor type and name set from one `as const` list,
replace indexed non-null assertions with destructuring, build the scope
chain without an assertion, and skip the enclosing-function owner lookup
when the decorator has no enclosing function. Key the stacked-decorator
verdicts by case name so a failure names its case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(python): resolve enclosing-function and deferred descriptor lookups

A class body reads a free name from the innermost enclosing function that
binds it (Language Reference 4.2.2). Evaluate that function's namespace
the same way as the class and module ones, so an unconditional
`from builtins import staticmethod` there resolves to the builtin. Any
other binding still shadows, including a local assigned only after the
class, which raises NameError rather than falling back to the builtin.

A class body inside a function runs whenever that function is called,
which can be any time after its top-level statement starts. Read module
state at that statement instead of after the whole module, so an earlier
`del` restores the builtin, and treat any later module override as
possibly visible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 08:12:52 +00:00
Gergő Magyar
0bcddec8e6
fix(python): keep uncertain decorated receivers unresolved (#3405)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(python): suppress uncertain decorated receivers

* fix(ci): keep uncertain Python receivers out of method arity

Unrecognized decorators now leave the receiver kind unproven, but the
first parameter is still the implicit receiver slot for ordinary bound
calls. Method extraction stopped stripping it, so decorated methods
reported one extra parameter and shifted capture arity metadata.

Share the uncertain-receiver classification between type-binding
synthesis and parameter extraction, then refresh the Python capture
golden and benchmark fingerprint for the intended capture change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 19:22:10 +00:00
azizur100389
4569910c79
fix(process): exclude Dart test entry points (#3407)
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-09-28 18:29:26 +00:00
Gergő Magyar
0ef3f28d0e
fix(python): avoid receiverless subtype targets
Fixes #3396
2026-09-28 17:34:26 +00:00