Issue 2502: a pnpm (or npm, Yarn, Bun) workspace member that declares its
entry with `main`, `module`, `types` or `exports`, like
`"main": "./src/index.ts"`, has no index file at its root, so
`import { caseStatus } from '@acme/core'` failed and its calls fell back to
name matching, where a same-named private function nearer the caller took
them. Issue 2456: a sibling package another declares as a path dependency
(`"acme-shared": "file:../common"`), with no root `workspaces`, was not
known as a package at all.
Root cause: workspace-packages.ts mapped a member's name to its folder only
(its header said it did not yet honour `exports` or `main`), and the
resolver probed `<folder>/index.*`. Path dependencies were read only from
ohpm manifests.
Fix:
- workspace-packages.ts reads each package's declared entries: `exports`
(subpaths, `*` patterns, fallback arrays, nested conditions, read with
import/require/node/module/default first, other conditions next, types
last) and the root's source/module/main/types/typings. It reads `file:`,
`link:` and `portal:` dependencies from every package.json in the
existing manifest walk: the root manifest's for every file, any other's
for the files under it, as npm installs them.
- The resolver takes the first declared target, `exports` before the
main-style fields, that the index holds or that is build output the
index lacks, read as the source it is built from (dist/, build/, lib/,
out/, es/, esm/, cjs/ mirrored under src/, then without a format folder).
Then the package's folder, as before, and a path into its build output
(`@acme/utils/lib/path`) read as its source. A file a local build left on
disk never wins over an indexed source, and no mapping links nothing.
- Sync: a full reconcile reads the manifests again and, when their digest
moved, re-opens every JavaScript-family reference, as a moved Python root
set does. A file added inside a package re-checks the imports of that
package that landed elsewhere in it. A full index drops the cached map.
Validation:
- __tests__/workspace-package-entries.test.ts (26 tests): 25 fail on main,
the control passes; all pass on the kernel and with CODEGRAPH_KERNEL=0.
Each of 17 rules removed in turn fails at least one test.
- Sync equals a fresh index in 15 CLI scenarios (entries, subpaths, a
member renamed, added or leaving the globs, file: deps added, removed or
retargeted, an entry's file appearing). On real repos: two vben manifest
edits (237 edge rows move) and a vite playground file: edit, synced graph
identical to a fresh index.
- A/B on 14 TS/JS repos and 2 controls: vben +2,955 edges, insomnia +2,222,
webdriverio +2,472, jest +1,700, nuxt +706, memfs +470, vite +251,
t3-turbo +47, social-app +40, vue3-antdv-admin +8, with name-matched
namesakes corrected; nitro, outline, pathe, c12, flask and gin are
byte-identical.
- Cost: fresh index within 3% (median of 3, interleaved); a full sync reads
the manifests, +30 to 160 ms on these monorepos.
Fixes#2502Fixes#2456
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pnpm's `packages/**/*` (folo) and npm's `libs/**/*` (bitwarden) list
member packages at any depth, `packages/plugin-*` and `apps/*/web` name
them by part of a folder name or by a folder below the wildcard, and a
negated glob (`!**/example`, `!examples/with-nx`) leaves some out. The
workspace loader listed only the folders directly under the text before
the first `*` and took a negation for a folder name, so folo's
`@follow/utils`, two levels under `packages/`, was no member and every
import of it counted as an outside package's.
Globs now match folder by folder: `*`, `?` and `{a,b}` within a name,
`**` across folders, breadth-first so a member nearer the root keeps a
name a deeper copy also declares. A wildcard never enters a dot-folder,
`node_modules` or `bower_components`, and a `**` skips `test` and `tests`
folders as pnpm's default ignores do; a folder the glob names literally
is always entered. What a negated glob matches is no member.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds ArkTS (.ets, HarmonyOS/OpenHarmony) as a first-class language:
full TypeScript-grade extraction via the harmony-contrib tree-sitter
grammar (MIT, vendored byte-identical from the tree-sitter-arkts 0.2.0
npm tarball), plus the ArkUI constructs that make HarmonyOS apps
traceable:
- @Component/@ComponentV2 structs with decorators from both grammar
positions; members extract as class members with qualified names.
- build() component trees: child instantiation edges via
arkui_component_expression, no synthesizer needed.
- Attribute chains emitted dot-prefixed and resolved ONLY against
@Extend/@Styles/@AnimatableExtend/@Builder helpers (unique-or-drop) —
bare-name fallthrough produced 36,840 wrong edges (17% of calls) on
the OpenHarmony samples monorepo. All four grammar chain shapes
handled, including the detached-chain forms.
- .onClick(this.handler) method-reference bindings.
- ohpm workspace modules: bare imports follow oh-package.json5 file:
deps (ambiguous names dropped), honoring each module's main entry —
which also lets .ts consumers resolve .ets modules.
- ArkUI dynamic-dispatch bridges, all provenance:'heuristic' with
wiring-site metadata: assignment-gated state->build() re-render
(V1 @State family + V2 @Local/@Provider/@Consumer),
@ohos.events.emitter emit->subscriber pairing on static event keys
(numeric ids same-file, named constants same-module, fan-out capped),
and router.pushUrl literal urls -> the target page's @Entry struct.
- $r/$rawfile resource intrinsics treated as built-ins; arkts joins the
web language family, value-reference edges, re-export chase, and the
other TS-applicable gates.
Also ships a language-agnostic index-completeness guard: indexAll
stamps index_state (indexing -> complete/partial/failed), reconciles
discovered vs accounted files (a loaded run silently dropped 37 files),
and codegraph status surfaces truncated/partial indexes in human and
--json output.
Validated on HarmoneyOpenEye (82 files), CoolMallArkTS (528, modular
ohpm + ArkUI V2), and openharmony/applications_app_samples (11,693
files, 202,890 nodes stable across re-index, attribute false-positive
audit 36,840 -> 588 residual all-plausible). Supersedes PRs #656 and
#988 with credit — both informed this implementation.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Component barrels (`export { default as X } from './X.svelte'`) and
monorepo workspace imports (`@scope/ui/widgets`) left the consumer↔component
edge uncreated, so live components showed a false `0 callers` — the canonical
dead-code signal — risking deletion of live code.
The Svelte default-barrel case broke at FOUR layers, each of which alone left
it unresolved:
- findExportedSymbol matched only function/class for a default export, never
`component` (Svelte/Vue SFCs are kind 'component').
- extractImportMappings had no svelte/vue branch, so SFC consumers produced
zero import mappings and resolveViaImport never ran.
- EXTENSION_RESOLUTION had no svelte/vue entry, so relative imports from an
SFC (`./lib` -> `/index.ts`) resolved to nothing.
- getReExports parsed the barrel in the CONSUMER's threaded language, so a
.svelte consumer made extractReExports bail on a .ts index barrel.
Workspace package-subpath barrels get a new workspace-packages module
(mirrors go-module/path-aliases): reads package.json `workspaces`
(npm/yarn/bun) + pnpm-workspace.yaml, maps member name->dir, resolves
`@scope/ui/widgets` -> `packages/ui/widgets`. Gated behind the workspaces
field so single-package repos are unaffected.
Bare `./`/`.` directory imports already resolved; covered with a regression
test. Verified both directions (callers/impact AND callees) for Svelte; Vue
script-level imports also resolve. 4 new tests; full suite green (1126).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>