diff --git a/CHANGELOG.md b/CHANGELOG.md index e516c1b2b..bde61afc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- In Astro projects, the component-name convention now applies only inside `.astro` files, where components are used. Before, a TypeScript type with a component's name was linked to that component: astrowind's `image?: Image` and `callToAction?: CallToAction` in its type declarations, and starlight's Playwright `page: Page` in its tests. - In JavaScript and TypeScript, the Express conventions for middleware (`validate…`, `auth`, `logger`, …), controllers and services now pick only the calling file's own declaration. Another file's is reached only through an `import` or `require`. Before, SvelteKit's remote functions linked `validate(arg)` to its config loader's `validate` in another package, and Express's examples linked `logger('dev')` to one example's `logger`. - An Objective-C class that conforms to a protocol of its own name now links to that protocol instead of to itself. Swift's naming conventions for views, models and controllers also no longer apply to Objective-C code. Before, every one of SDWebImage's `@interface SDDiskCache : NSObject `-style classes implemented itself. - In Solidity, a call to an overloaded function now reaches the overload whose parameter count it fits, as in the other languages with overloading. Before, OpenZeppelin's `_checkRole(role, _msgSender())` inside the one-argument `_checkRole(bytes32 role)` was linked to itself. diff --git a/__tests__/astro-component-markup-only.test.ts b/__tests__/astro-component-markup-only.test.ts new file mode 100644 index 000000000..117041c2e --- /dev/null +++ b/__tests__/astro-component-markup-only.test.ts @@ -0,0 +1,55 @@ +/** + * An Astro component is used in Astro markup, so the component name + * heuristic applies to `.astro` files only: astrowind's `types.d.ts` + * (`image?: Image`, `callToAction?: CallToAction`) and starlight's + * Playwright `page: Page` went to `Image.astro`, `CallToAction.astro` and + * `Page.astro`. + */ +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +let root = ''; +let cg: CodeGraph; + +beforeAll(async () => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-astro-markup-')); + const files: Record = { + 'package.json': JSON.stringify({ name: 'site', private: true, dependencies: { astro: '^5.0.0' }, devDependencies: { '@playwright/test': '^1.0.0' } }), + 'astro.config.mjs': 'export default {};\n', + 'src/components/Page.astro': `--- +const { title } = Astro.props; +--- +
{title}
+`, + 'tests/test-utils.ts': `import { type Page } from '@playwright/test'; + +export class StarlightPage { + private readonly page: Page; + constructor(page: Page) { + this.page = page; + } +} +`, + }; + 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); + } + cg = await CodeGraph.init(root, { index: true }); +}); + +afterAll(() => { + cg?.close(); + if (root) fs.rmSync(root, { recursive: true, force: true }); +}); + +describe('Astro components', () => { + it('are never what a TypeScript type name means', () => { + const ids = cg.getNodesInFile('tests/test-utils.ts').map((n) => n.id); + const targets = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind !== 'contains').map((e) => cg.getNode(e.target)!.filePath); + expect(targets).not.toContain('src/components/Page.astro'); + }); +}); diff --git a/src/resolution/frameworks/astro.ts b/src/resolution/frameworks/astro.ts index 63e1af132..4645335ba 100644 --- a/src/resolution/frameworks/astro.ts +++ b/src/resolution/frameworks/astro.ts @@ -74,7 +74,10 @@ export const astroResolver: FrameworkResolver = { // Pattern 3: Component references (PascalCase) — resolve to component // nodes. Template tags arrive as `references`, frontmatter expression // usages as `calls`. + // Only from Astro markup: a `.ts` file's `Page` (Playwright's) or a + // declaration file's `image?: Image` is no `Page.astro` / `Image.astro`. if ( + ref.filePath.endsWith('.astro') && isPascalCase(ref.referenceName) && (ref.referenceKind === 'references' || ref.referenceKind === 'calls') ) {