/** * `$this->m()`, `self::m()`, `static::m()` and `parent::m()` name a method of * the class the call is written in, a class it extends, or a trait any of them * uses — never another class's method that shares the name. * * Drupal core's tests call PHPUnit's `$this->assertEquals()` (a vendor base * class), which went to the one in-repo `assertEquals`, a comparator's, 8,832 * times; `$this->t()` from StringTranslationTrait went to `Views::t`. */ 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('PHP calls on $this, self and parent', () => { it('reach the class hierarchy and its traits, and nothing outside it', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-php-this-')); roots.push(root); const files: Record = { 'core/lib/StringTranslationTrait.php': `helper(); return $this->t('Hi'); } } `, 'core/tests/NodeTest.php': `assertEquals(1, 1); } } `, // Laravel's own tests extend Orchestra's TestCase, which isn't in the // repository but composes the repository's testing traits. 'src/Testing/InteractsWithTime.php': `getValue($attribute); } } `, 'src/Validation/Validator.php': `book(); } } `, 'app/Page.php': `travelTo('2000-01-02'); } } `, }; 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 callees = (file: string, method: string) => { const from = cg.getNodesInFile(file).find((n) => n.kind === 'method' && n.name === method)!; return cg .getOutgoingEdges(from.id) .filter((e) => e.kind === 'calls') .map((e) => cg.getNode(e.target)!.qualifiedName) .sort(); }; expect(callees('core/modules/my/MyForm.php', 'setUp')).toEqual([ 'Drupal\\Core\\Form::FormBase::helper', 'Drupal\\Core\\Form::FormBase::setUp', 'Drupal\\Core\\StringTranslation::StringTranslationTrait::t', ]); // PHPUnit's TestCase is not in the repository, and a comparator's // `assertEquals` is no method of it. expect(callees('core/tests/NodeTest.php', 'testTitle')).toEqual([]); expect(callees('src/Validation/ValidatesAttributes.php', 'validateSame')).toEqual(['Illuminate\\Validation::Validator::getValue']); expect(callees('app/Entity.php', 'parentBook')).toEqual(['BookStack\\Entities::Page::book']); // Past an ancestor this cannot see, the repository's traits still count. expect(callees('tests/QueueTest.php', 'testDelay')).toEqual(['Illuminate\\Foundation\\Testing\\Concerns::InteractsWithTime::travelTo']); } finally { cg.close(); } }); });