mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 03:08:45 +08:00
fix(r): a test file's top-level variables stay in that file (#2144)
testthat runs each test file in an environment of its own, so a variable a test assigns is invisible to the package and to other tests. ggplot2's `c <- ggplot(…)` in test-facet-grid-.R was the one node named `c`, and took 2,455 of the package's `c(…)` calls (the sweep's top-depended symbol). isVisibleAcrossFiles rejects an R variable/constant under `tests/` from another file — except `helper-*.R` / `setup-*.R`, which testthat sources for every test. (A first cut that limited every R call to function nodes lost ggplot2's factory-defined `geom_point <- make_constructor(…)`, dplyr's `summarize <- summarise` and shiny's aliases.) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Fixes
|
||||
|
||||
- In R, a variable assigned at the top of a test file is no longer linked from the package or from other tests, because testthat runs each test file in its own environment. On ggplot2, a test's `c <- ggplot(…)` used to collect 2,455 of the package's `c(…)` calls. Variables in `helper-*.R` and `setup-*.R`, which testthat loads for every test, are still shared.
|
||||
- In Lua and Luau, a `local` variable or function is no longer linked from other files, since it's only visible in the file that declares it. On kong, a spec helper's `local it = it` used to collect every other spec file's `it(…)` (over 4,000 links), and one module's `local ipairs = ipairs` took the whole project's `ipairs(…)`. Global functions are still linked across files.
|
||||
- PHP calls on `$this`, `self`, `static` and `parent` now reach the method of the class they're written in, a class it extends, or a trait any of them uses, with parent classes and traits found through the file's `namespace` and `use` imports. On Drupal core, `$this->assertEquals()` (PHPUnit's) used to link to an unrelated comparator class 8,832 times, `$this->assertSession()` to a JavaScript-test base class, and `$this->t()` to `Views::t`. They now reach `UiHelperTrait` and `StringTranslationTrait`, or nothing when the method lives in a package outside the repository. A call inside a trait, and a base class calling a method its subclass defines, keep their links. Re-index PHP projects after upgrading.
|
||||
- In PHP, CFML, Pascal, COBOL and VB.NET, a call written in a different case than its definition now resolves, like `formatprice()` for `FormatPrice` or CFML's `ToMatchWithCase()` for `toMatchWithCase`. These languages ignore case in names, but such calls used to be dropped before resolution.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* testthat runs each test file in an environment of its own: a variable one
|
||||
* test assigns is invisible to the package and to other tests. ggplot2's
|
||||
* `c <- ggplot(…)` in one test took the package's 2,455 `c(…)` calls. A
|
||||
* `helper-*.R` file is sourced for every test, so its bindings stay shared,
|
||||
* and a package function defined through a factory
|
||||
* (`geom_point <- make_constructor(…)`) stays callable.
|
||||
*/
|
||||
import { describe, it, expect, afterAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CodeGraph } from '../src';
|
||||
|
||||
const roots: string[] = [];
|
||||
afterAll(() => {
|
||||
for (const r of roots.splice(0)) fs.rmSync(r, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('R test-file bindings', () => {
|
||||
it('stay in their file, except helpers; package bindings stay callable', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-r-tests-'));
|
||||
roots.push(root);
|
||||
const files: Record<string, string> = {
|
||||
DESCRIPTION: 'Package: demo\n',
|
||||
'R/geom-point.R': `geom_point <- make_constructor(GeomPoint)
|
||||
table_of <- function() {
|
||||
c("a", "b")
|
||||
}
|
||||
`,
|
||||
'tests/testthat/helper-s3.R': `quux <- structure(list(), class = "quux")
|
||||
`,
|
||||
'tests/testthat/test-scales.R': `c <- ggplot(df, aes(x, y))
|
||||
test_that("works", {
|
||||
geom_point()
|
||||
quux()
|
||||
})
|
||||
`,
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, rel), content);
|
||||
}
|
||||
const cg = await CodeGraph.init(root, { index: true });
|
||||
try {
|
||||
const targets = (file: string) =>
|
||||
cg
|
||||
.getOutgoingEdgesFrom(cg.getNodesInFile(file).map((n) => n.id), ['calls'])
|
||||
.map((e) => `${cg.getNode(e.target)!.name}@${cg.getNode(e.target)!.filePath}`)
|
||||
.sort();
|
||||
expect(targets('R/geom-point.R')).toEqual([]);
|
||||
expect(targets('tests/testthat/test-scales.R')).toEqual(['geom_point@R/geom-point.R', 'quux@tests/testthat/helper-s3.R']);
|
||||
} finally {
|
||||
cg.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -944,6 +944,11 @@ export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, contex
|
||||
return ref.filePath.startsWith(owner + '/');
|
||||
}
|
||||
if (PRIVATE_IS_FILE_LOCAL.has(lang)) return candidate.visibility !== 'private';
|
||||
// An R test file runs in an environment of its own (testthat): its top-level
|
||||
// `c <- ggplot(…)` is not what the package's 2,843 `c(…)` calls mean. The
|
||||
// `helper-*.R` / `setup-*.R` files are sourced for every test, so theirs are shared.
|
||||
if (lang === 'r' && (candidate.kind === 'variable' || candidate.kind === 'constant') &&
|
||||
/(?:^|\/)tests?\//.test(candidate.filePath) && !/(?:^|\/)(?:helper|setup)[^/]*\.[rR]$/.test(candidate.filePath)) return false;
|
||||
// A Lua `local` belongs to its chunk: kong's spec helpers' `local it = it`
|
||||
// took busted's `it(…)` in every other spec file, 4,166 times.
|
||||
if ((lang === 'lua' || lang === 'luau') && isLuaLocal(candidate, context)) return false;
|
||||
|
||||
Reference in New Issue
Block a user