diff --git a/gitnexus/src/core/ingestion/type-extractors/r.ts b/gitnexus/src/core/ingestion/type-extractors/r.ts index b87e4d03e..16fe3202a 100644 --- a/gitnexus/src/core/ingestion/type-extractors/r.ts +++ b/gitnexus/src/core/ingestion/type-extractors/r.ts @@ -66,8 +66,9 @@ const collectRoxygenBlock = (node: SyntaxNode): string => { /** * Split a roxygen block into its `@param` tags. A tag's description is the * rest of its line plus every following line up to the next `@tag`, joined by - * single spaces. A tag whose name is not one plain identifier (`x,y`, `...`, - * `na.rm`) is skipped, and its continuation lines belong to no tag. + * single spaces. A tag whose name is not one identifier (`x,y`, `...`) is skipped, + * and its continuation lines belong to no tag. Dots are part of an R name + * (`na.rm`, `.data`). */ const parseRoxygenParams = (block: string): RoxygenParam[] => { const params: RoxygenParam[] = []; @@ -85,7 +86,7 @@ const parseRoxygenParams = (block: string): RoxygenParam[] => { if (tag) { flush(); if (tag[1] !== 'param') continue; - const head = /^(\w+)(?:[ \t]+(.*))?$/.exec(tag[2] ?? ''); + const head = /^([\w.]*\w[\w.]*)(?:[ \t]+(.*))?$/.exec(tag[2] ?? ''); if (!head) continue; name = head[1]; if (head[2]) parts.push(head[2]); diff --git a/gitnexus/test/unit/type-env-r.test.ts b/gitnexus/test/unit/type-env-r.test.ts index 8dddf03e8..e888facfc 100644 --- a/gitnexus/test/unit/type-env-r.test.ts +++ b/gitnexus/test/unit/type-env-r.test.ts @@ -509,13 +509,56 @@ save <- function(repo, backup) { }); }); + describe('dotted formal names', () => { + it.each([ + ['na.rm', 'na.rm'], + ['a leading dot', '.data'], + ['dots and underscores', 'max_n.rows'], + ])('binds the documented type of %s', (_label, formal) => { + const tree = parse(` +Data <- R6::R6Class("Data", public = list(fit = function() 1)) + +#' @param ${formal} Data +#' @param other Data +run <- function(${formal}, other) { + ${formal} +} +`); + const typeEnv = buildTypeEnv(tree, SupportedLanguages.R); + expect(flatGet(typeEnv, formal)).toBe('Data'); + expect(flatGet(typeEnv, 'other')).toBe('Data'); + }); + + it('keeps the braced form and the continuation lines of a dotted name', () => { + const tree = parse(` +Data <- R6::R6Class("Data", public = list(fit = function() 1)) + +#' @param na.rm {Data} +#' second line of prose +run <- function(na.rm) na.rm +`); + expect(flatGet(buildTypeEnv(tree, SupportedLanguages.R), 'na.rm')).toBe('Data'); + }); + + it('binds nothing for a dotted name the function does not have', () => { + const tree = parse(` +Data <- R6::R6Class("Data", public = list(fit = function() 1)) + +#' @param na.rm Data +run <- function(x) x +`); + const typeEnv = buildTypeEnv(tree, SupportedLanguages.R); + expect(flatGet(typeEnv, 'na.rm')).toBeUndefined(); + expect(flatGet(typeEnv, 'x')).toBeUndefined(); + }); + }); + describe('forms that are not bound (guards)', () => { it.each([ ['\\code{Data} markup', "#' @param df \\code{Data}", ['df']], ['\\linkS4class{Data} markup', "#' @param df \\linkS4class{Data}", ['df']], ['a comma-separated name list', "#' @param x,y Data", ['x', 'y']], ['the dots parameter', "#' @param ... Data", ['...']], - ['a dotted parameter name', "#' @param na.rm Data", ['na.rm']], ])('does not bind %s', (_label, tag, names) => { const tree = parse(` Data <- R6::R6Class("Data", public = list(fit = function() 1))