mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 00:38:18 +08:00
fix(sync): an import of a folder further up links its index file when it appears (#2669)
An import that names a folder only through where it is written (`.`, `..`, `../..`) is parked under no key, so sync tries each where it is written when files are added (#2528). That lookup listed `.`, `./`, `..` and `../` only, so `import { Excalidraw } from "../.."` in excalidraw's tests, or any import of a folder two or more levels up, stayed unlinked after its folder's `index` file was added or restored, though a fresh index links it. It now lists the folder up to twelve levels above, with or without the trailing slash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Dart, a call written through an import prefix, like `http.get(…)` after `import 'package:http/http.dart' as http;`, or `utils.Listener()` and `kit.Box.create()` through a prefix of your own, now links to what the library imported with that prefix exports, following its `export` chains, `show` and `hide`, and to nothing when that library is a package outside your project. Before, the prefix was taken for a variable, so `http.get(…)` could link to an unrelated `Http` class's `get` method, and calls made through a prefix into your own libraries were missing from `callers` and impact. Re-index Dart projects after upgrading.
|
||||
- `codegraph sync` and the file watcher now link an import to the file it names when that file is added after the importing file was indexed, or comes back after being deleted: `import 'package:app/b.dart';` in Dart, `import './side'` in JavaScript or TypeScript, `#include "net/socket.h"` in C and C++, `require 'inc/db.php'` in PHP, `import pkg.mod` in Python, a Lua `require`, or a C# `using` of a namespace declared later. Before, the import stayed unlinked until the importing file itself changed or the project was re-indexed, so `codegraph affected` and the files listed as using the new file missed it. Re-index after upgrading.
|
||||
- `codegraph sync` and the file watcher now also link the imports of an added or restored JavaScript or TypeScript file whose name hundreds of other imports share, like a project's own `utils.ts` among `jotai/utils` and `@scope/pkg/utils` imports, or `assert.ts` in a project full of `import * as assert from 'assert'`. Before, a name that common was skipped as noise no single file could answer, so in a large project the file's own `./utils` and `~/settings/utils` imports stayed unlinked until the importing files changed or the project was re-indexed.
|
||||
- `codegraph sync` and the file watcher now link an import of a folder two or more levels up, like `import { Excalidraw } from "../.."` in excalidraw's tests, to that folder's `index` file when the file is added after the importing file was indexed, or comes back after being deleted. Before, only an import of the file's own folder or the one above (`.`, `..`) was relinked; one reaching further up stayed unlinked until the importing file changed or the project was re-indexed.
|
||||
- `codegraph sync` and the file watcher now link a navigation call to its page when the page or route is added after the call was indexed, or comes back after being removed: `history.push('/login')` and `navigate('/login')` with React Router, `router.push('/login')` and `redirect('/login')` in Next.js, Expo Router, Vue Router, TanStack Router and SvelteKit (`goto('/login')`), and `this.router.navigate(['/login'])` and `routerLink="/login"` in Angular. Before, the call stayed unlinked, or linked to a catch-all or `:id` page that answered it in the meantime, until its own file changed or the project was re-indexed, so the step from that call to the page it opens was missing or wrong. A route whose path changes without its own file changing, like one built from a path constant kept in another file, now also lets go of the calls that named its old path. Re-index after upgrading.
|
||||
- JSON files in a `templates` or `sections` folder are now read as Shopify Liquid only inside a Shopify theme, where the folder that contains `templates` or `sections` also has `layout/theme.liquid` or `config/settings_schema.json`. Before, all such JSON was, so a .NET project template's `template.json`, a schematic or a CMS's content showed up in the index as an empty Liquid file, and one that lists `sections` could be linked to an unrelated Liquid file of the same name. Shopify themes, including one kept in a subfolder of a repository, are indexed as before, and the next sync removes JSON that an earlier version indexed this way.
|
||||
- In a repository that holds more than one Shopify theme, a theme's `{% render %}`, `{% include %}` and `{% section %}` tags and the sections its JSON templates list now link only to that theme's own snippets and sections, which is where Shopify looks for them. Before, a section or snippet the theme doesn't have was linked to another theme's file of the same name, and a theme kept in a subfolder could link to the root theme's copy instead of its own. A reference the theme can't satisfy now links to nothing. Projects with a single theme are indexed as before. Re-index projects with several Shopify themes after upgrading.
|
||||
|
||||
@@ -81,6 +81,17 @@ const CASES: Case[] = [
|
||||
from: 'a.js',
|
||||
expected: ['file side.js', 'file def.js', 'file req.js', 'file lib/index.js'],
|
||||
},
|
||||
{
|
||||
// A folder further up than the one above names it only through where it
|
||||
// is written, so it waits under no key of the file that appears.
|
||||
name: 'TypeScript (a folder further up)',
|
||||
initial: {
|
||||
'src/a/b/c/m.ts': "import { x } from '../..';\nimport { y } from '../../';\nimport { z } from '../../..';\nexport const v = [x, y, z];\n",
|
||||
},
|
||||
added: { 'src/a/index.ts': 'export const x = 1;\nexport const y = 2;\n', 'src/index.ts': 'export const z = 3;\n' },
|
||||
from: 'src/a/b/c/m.ts',
|
||||
expected: ['file src/a/index.ts', 'file src/index.ts'],
|
||||
},
|
||||
{
|
||||
name: 'C',
|
||||
initial: { 'src/main.c': '#include "a/b.h"\n#include "c.h"\n#include <stdio.h>\nint main(void) { return 0; }\n' },
|
||||
|
||||
+19
-4
@@ -45,6 +45,20 @@ function isLowValueFile(filePath: string, generated?: ReadonlySet<string>): bool
|
||||
|
||||
const SQLITE_PARAM_CHUNK_SIZE = 500;
|
||||
|
||||
/**
|
||||
* How an import names a folder only through where it is written: `.`, or a
|
||||
* folder up to twelve levels above (`..`, `../..`, with or without the
|
||||
* trailing `/`).
|
||||
*/
|
||||
const FOLDER_SPECIFIERS: readonly string[] = (() => {
|
||||
const names = ['.', './'];
|
||||
for (let levels = 1; levels <= 12; levels++) {
|
||||
const up = Array.from({ length: levels }, () => '..').join('/');
|
||||
names.push(up, `${up}/`);
|
||||
}
|
||||
return names;
|
||||
})();
|
||||
|
||||
/** A name a member reference's receiver can be: not a file's name, nor a `module:` key. */
|
||||
const RECEIVER_NAME = /^[A-Za-z_$][\w$]*$/;
|
||||
|
||||
@@ -4222,16 +4236,17 @@ export class QueryBuilder {
|
||||
|
||||
/**
|
||||
* Failed imports of the folder the importing file is in or under (`.`,
|
||||
* `..`). Their tail is empty, since they name a folder only through where
|
||||
* they are written, so no added file's keys find them.
|
||||
* `..`, `../..`). Their tail is empty, since they name a folder only
|
||||
* through where they are written, so no added file's keys find them.
|
||||
*/
|
||||
getFailedFolderImports(): UnresolvedReference[] {
|
||||
const placeholders = FOLDER_SPECIFIERS.map(() => '?').join(',');
|
||||
return (this.db
|
||||
.prepare(
|
||||
`SELECT * FROM unresolved_refs
|
||||
WHERE status = 'failed' AND reference_kind = 'imports' AND reference_name IN ('.', './', '..', '../')`
|
||||
WHERE status = 'failed' AND reference_kind = 'imports' AND reference_name IN (${placeholders})`
|
||||
)
|
||||
.all() as UnresolvedRefRow[])
|
||||
.all(...FOLDER_SPECIFIERS) as UnresolvedRefRow[])
|
||||
.map((row) => ({
|
||||
fromNodeId: row.from_node_id,
|
||||
referenceName: row.reference_name,
|
||||
|
||||
+3
-2
@@ -1064,8 +1064,9 @@ export class CodeGraph {
|
||||
// to an added file (`@excalidraw/common` to `packages/common/src/
|
||||
// index.ts`) waits under its last segment, which that file's keys
|
||||
// never give: look its failed imports up by the specifier itself.
|
||||
// An import of the importer's own folder or the one above (`.`,
|
||||
// `..`) waits under no key at all, so each is tried where written.
|
||||
// An import of the importer's own folder or one above it (`.`,
|
||||
// `..`, `../..`) waits under no key at all, so each is tried
|
||||
// where written.
|
||||
const addedFiles = new Set(result.addedFilePaths ?? []);
|
||||
const resolvesToAdded = (specifier: string, fromFile: string, language: Language): boolean => {
|
||||
const file = this.resolver.resolveModuleFile(specifier, fromFile, language);
|
||||
|
||||
Reference in New Issue
Block a user