From 7ad2ac2bc01491d7f3e341e6d5ececab6ea4a6ff Mon Sep 17 00:00:00 2001 From: Mick Cooney Date: Mon, 12 Oct 2026 00:00:47 +0100 Subject: [PATCH] test(r): pin the non-indexed R file types and the experimental classification - Replace the vacuous ".Rprofile/.Renviron are not indexed" test (no fixture contained such files) with an r-non-indexed fixture holding .Rprofile, .Renviron, .Rmd and .qmd that define functions next to a plain .R positive control; the Rmd/qmd bodies are valid R so routing them to the R parser would fail the test. - Add getLanguageFromFilename negatives for those names. - Add R to the query-compilation smoke list. - Assert R's LanguageClassifications value ('experimental'), which the docs claim but nothing checked. --- .../lang-resolution/r-non-indexed/.Renviron | 1 + .../lang-resolution/r-non-indexed/.Rprofile | 1 + .../r-non-indexed/analysis.Rmd | 3 ++ .../lang-resolution/r-non-indexed/real.R | 2 + .../lang-resolution/r-non-indexed/report.qmd | 2 + .../integration/query-compilation.test.ts | 1 + gitnexus/test/integration/resolvers/r.test.ts | 48 ++++++++++++++----- gitnexus/test/unit/ingestion-utils.test.ts | 12 +++++ .../unit/r-language-classification.test.ts | 16 +++++++ 9 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Renviron create mode 100644 gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Rprofile create mode 100644 gitnexus/test/fixtures/lang-resolution/r-non-indexed/analysis.Rmd create mode 100644 gitnexus/test/fixtures/lang-resolution/r-non-indexed/real.R create mode 100644 gitnexus/test/fixtures/lang-resolution/r-non-indexed/report.qmd create mode 100644 gitnexus/test/unit/r-language-classification.test.ts diff --git a/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Renviron b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Renviron new file mode 100644 index 000000000..0e9b57512 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Renviron @@ -0,0 +1 @@ +R_LIBS_USER=~/R/library diff --git a/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Rprofile b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Rprofile new file mode 100644 index 000000000..fa1f846ea --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/.Rprofile @@ -0,0 +1 @@ +rprofile_fn <- function() 1 diff --git a/gitnexus/test/fixtures/lang-resolution/r-non-indexed/analysis.Rmd b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/analysis.Rmd new file mode 100644 index 000000000..1941cdafd --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/analysis.Rmd @@ -0,0 +1,3 @@ +# Valid R on purpose: if this extension were ever routed to the R parser the +# function below would be indexed, so the negative test can actually fail. +rmd_fn <- function() 1 diff --git a/gitnexus/test/fixtures/lang-resolution/r-non-indexed/real.R b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/real.R new file mode 100644 index 000000000..540e3f529 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/real.R @@ -0,0 +1,2 @@ +# Positive control: a plain .R file is indexed. +indexed_control <- function() 1 diff --git a/gitnexus/test/fixtures/lang-resolution/r-non-indexed/report.qmd b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/report.qmd new file mode 100644 index 000000000..9762f0f27 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/r-non-indexed/report.qmd @@ -0,0 +1,2 @@ +# Valid R on purpose (see analysis.Rmd). +qmd_fn <- function() 1 diff --git a/gitnexus/test/integration/query-compilation.test.ts b/gitnexus/test/integration/query-compilation.test.ts index 6652a59bf..71d06a1bd 100644 --- a/gitnexus/test/integration/query-compilation.test.ts +++ b/gitnexus/test/integration/query-compilation.test.ts @@ -34,6 +34,7 @@ describe('Query compilation smoke tests', () => { [SupportedLanguages.Kotlin]: 'Test.kt', [SupportedLanguages.Swift]: 'test.swift', [SupportedLanguages.Dart]: 'test.dart', + [SupportedLanguages.R]: 'test.R', }; // Known query compilation failures — remove from this set as PRs fix them diff --git a/gitnexus/test/integration/resolvers/r.test.ts b/gitnexus/test/integration/resolvers/r.test.ts index 36ccdba34..b21f572df 100644 --- a/gitnexus/test/integration/resolvers/r.test.ts +++ b/gitnexus/test/integration/resolvers/r.test.ts @@ -374,20 +374,44 @@ describe('R function definitions and calls', () => { expect(itemsField).toBeDefined(); expect(itemsField!.properties.visibility).toBe('public'); }); +}); - // --- Negative tests --- +/** + * Files the R provider must not index. The `r-non-indexed` fixture holds a + * `.Rprofile`, `.Renviron`, `.Rmd` and `.qmd`, each defining a function, next to a plain + * `real.R` positive control (docs/languages/r-provider.md, "Limits"). + */ +describe('R files that are not indexed', () => { + let result: PipelineResult; - it('does not index .Rprofile or .Renviron files', () => { - const allNodes: string[] = []; - result.graph.forEachNode((n) => { - if ( - n.properties.filePath?.includes('.Rprofile') || - n.properties.filePath?.includes('.Renviron') - ) { - allNodes.push(n.properties.name); - } - }); - expect(allNodes).toHaveLength(0); + beforeAll(async () => { + result = await runPipelineFromRepo(path.join(FIXTURES, 'r-non-indexed'), () => {}); + }, 60000); + + it('indexes the plain .R control file (so the negatives below are not vacuous)', () => { + expect(getNodesByLabel(result, 'Function')).toEqual(['indexed_control']); + }); + + // The scanner may still record a bare File node for these; what must not exist is any + // parsed symbol (Function, Variable, ...) from them. + it.each(['.Rprofile', '.Renviron', 'analysis.Rmd', 'report.qmd'])( + 'creates no symbol node for %s', + (name) => { + const hits: string[] = []; + result.graph.forEachNode((n) => { + if (n.label !== 'File' && n.properties.filePath?.endsWith(name)) { + hits.push(`${n.label}:${n.properties.name}`); + } + }); + expect(hits).toEqual([]); + }, + ); + + it('defines no function from the non-indexed files', () => { + const functions = getNodesByLabel(result, 'Function'); + expect(functions).not.toContain('rprofile_fn'); + expect(functions).not.toContain('rmd_fn'); + expect(functions).not.toContain('qmd_fn'); }); }); diff --git a/gitnexus/test/unit/ingestion-utils.test.ts b/gitnexus/test/unit/ingestion-utils.test.ts index 08ea6d5d8..d25c7aa73 100644 --- a/gitnexus/test/unit/ingestion-utils.test.ts +++ b/gitnexus/test/unit/ingestion-utils.test.ts @@ -159,6 +159,18 @@ describe('getLanguageFromFilename', () => { it.each(['.r', '.R'])('detects %s files', (ext) => { expect(getLanguageFromFilename(`analysis${ext}`)).toBe(SupportedLanguages.R); }); + + // docs/languages/r-provider.md: only `.r` / `.R` are handled. + it.each([ + 'analysis.Rmd', + 'report.qmd', + '.Rprofile', + '.Renviron', + 'Rprofile.site', + 'x.Rhistory', + ])('returns null for %s', (name) => { + expect(getLanguageFromFilename(name)).toBeNull(); + }); }); describe('unsupported', () => { diff --git a/gitnexus/test/unit/r-language-classification.test.ts b/gitnexus/test/unit/r-language-classification.test.ts new file mode 100644 index 000000000..e4edf2ff9 --- /dev/null +++ b/gitnexus/test/unit/r-language-classification.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { LanguageClassifications, isProductionLanguage } from 'gitnexus-shared'; +import { SupportedLanguages } from '../../src/config/supported-languages.js'; + +// docs/languages/r-provider.md describes R as an experimental provider. The classification +// map is type-forced to contain R but nothing else asserts its value, so a silent +// promotion to 'production' (a governance change) would otherwise go unnoticed. +describe('R language classification', () => { + it('classifies R as experimental', () => { + expect(LanguageClassifications[SupportedLanguages.R]).toBe('experimental'); + }); + + it('does not treat R as a production language', () => { + expect(isProductionLanguage(SupportedLanguages.R)).toBe(false); + }); +});