mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 05:41:18 +08:00
fix(vue): the Vue resolver only resolves a Vue app's scripts (#2210)
Framework resolvers see every reference of every language, and the Vue resolver had no language guard: its PascalCase-component rule took mealie's Python `QueryFilterBuilder(...)` (an unresolved import) to the `QueryFilterBuilder.vue` component at 0.8, and its macro / auto-import rules matched any language's `ref(...)` or `computed(...)`. It now answers only refs from vue / javascript / typescript / tsx / jsx files. mealie: 6 Python calls reroute to the Python class; halo, elk, element-plus unchanged. 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 a project with a Vue front end, the backend's calls no longer land on Vue components. mealie's Python `QueryFilterBuilder(…)` had been linking to the `QueryFilterBuilder.vue` component. The Vue rules (compiler macros, Nuxt auto-imports, components by name) now apply only to the app's scripts.
|
||||
- Production code no longer links to a same-named symbol inside a test suite, since tests aren't built into the program. Before, typeorm's `Record<string, …>` types reached a test entity called `Record`, and okhttp's sample `@Override` annotations reached a test's nested `Override` class. Test-support code a project ships, such as a `testing/` folder or a `*-test` module, stays in reach.
|
||||
- A Dart call like `ext.endsWith(".avi")` or `map.putIfAbsent(…)` on a value whose type isn't known now counts as Dart's own String, List or Map method. It no longer lands on a project method of the same name. A Dart extension is also matched by the type it extends, not by its own name, so getx's `ext.endsWith` on a String stopped reaching `extension RxStringExt on Rx<String>`.
|
||||
- Kotlin calls inside a DSL lambda now reach the lambda's own receiver. That receiver comes from the function the lambda is passed to, even through a typealias like koin's `Definition<T> = Scope.(…) -> T`. It also wins over other types the project uses as lambda receivers elsewhere. On koin, over 2,000 `get()` calls in `single { … }` now reach `Scope.get` instead of `Koin.get`; on Exposed, `varchar(…)` inside a table object reaches `Table.varchar`; on kotlinx.coroutines, `launch { }` reaches `CoroutineScope.launch`.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* The Vue resolver's rules — compiler macros, Nuxt auto-imports, PascalCase
|
||||
* components — are a Vue app's scripts', never another language's: mealie's
|
||||
* Python `QueryFilterBuilder(...)` resolved to the `QueryFilterBuilder.vue`
|
||||
* component instead of the Python class it constructs.
|
||||
*/
|
||||
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-vue-lang-'));
|
||||
const files: Record<string, string> = {
|
||||
'package.json': JSON.stringify({ name: 'app', dependencies: { vue: '^3' } }),
|
||||
'frontend/components/QueryFilterBuilder.vue': `<template><div /></template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ value: string }>();
|
||||
</script>
|
||||
`,
|
||||
'backend/query.py': `class QueryFilterBuilder:
|
||||
def __init__(self, raw):
|
||||
self.raw = raw
|
||||
`,
|
||||
'backend/test_query.py': `from mealie.services.query_filter.builder import QueryFilterBuilder
|
||||
|
||||
|
||||
def test_builder():
|
||||
return QueryFilterBuilder("x")
|
||||
`,
|
||||
};
|
||||
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('the Vue resolver', () => {
|
||||
it('leaves a Python call to its own class alone', () => {
|
||||
const ids = cg.getNodesInFile('backend/test_query.py').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('frontend/components/QueryFilterBuilder.vue');
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,9 @@ import { Node } from '../../types';
|
||||
import { FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types';
|
||||
import { dependsOn } from './package-deps';
|
||||
|
||||
/** The languages a Vue app's scripts are written in. */
|
||||
const VUE_SCRIPT_LANGUAGES: ReadonlySet<string> = new Set(['vue', 'javascript', 'typescript', 'tsx', 'jsx']);
|
||||
|
||||
/**
|
||||
* Vue 3 compiler macros — compiler-provided, not user code
|
||||
*/
|
||||
@@ -102,6 +105,11 @@ export const vueResolver: FrameworkResolver = {
|
||||
},
|
||||
|
||||
resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
|
||||
// Vue's macros, auto-imports and components are a script's, never a
|
||||
// backend's: mealie's Python `QueryFilterBuilder(...)` is not the
|
||||
// `QueryFilterBuilder.vue` component.
|
||||
if (!VUE_SCRIPT_LANGUAGES.has(ref.language)) return null;
|
||||
|
||||
// Pattern 1: Vue compiler macros (defineProps, defineEmits, etc.)
|
||||
if (VUE_COMPILER_MACROS.has(ref.referenceName)) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user