@component-compass/plugin-core 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,29 @@ export type Diagnostic = {
28
28
  filePath: string;
29
29
  tagName: string;
30
30
  conflictsWith: string;
31
+ } | {
32
+ code: "unresolved-import";
33
+ severity: "warning";
34
+ filePath: string;
35
+ line: number;
36
+ column: number;
37
+ specifier: string;
38
+ } | {
39
+ code: "unresolved-reference";
40
+ severity: "info";
41
+ filePath: string;
42
+ line: number;
43
+ column: number;
44
+ symbol: string;
45
+ memberChain: string[];
46
+ } | {
47
+ code: "cycle-in-resolution";
48
+ severity: "warning";
49
+ filePath: string;
50
+ line: number;
51
+ column: number;
52
+ symbol: string;
53
+ depth: number;
31
54
  } | {
32
55
  code: "cycle-detected";
33
56
  severity: "warning";
@@ -31,6 +31,12 @@ function dedupKey(d) {
31
31
  return `${d.code}::${d.filePath}::${d.line ?? ""}::${d.column ?? ""}::${d.tagName}`;
32
32
  case "local-index-duplicate-tag":
33
33
  return `${d.code}::${d.filePath}::${d.tagName}`;
34
+ case "unresolved-import":
35
+ return `${d.code}::${d.filePath}::${d.line}::${d.column}::${d.specifier}`;
36
+ case "unresolved-reference":
37
+ return `${d.code}::${d.filePath}::${d.line}::${d.column}::${d.symbol}::${d.memberChain.join(".")}`;
38
+ case "cycle-in-resolution":
39
+ return `${d.code}::${d.filePath}::${d.symbol}`;
34
40
  case "cycle-detected":
35
41
  return `${d.code}::${d.filePath}::${d.exportName}`;
36
42
  case "chain-too-deep":
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostic.js","sourceRoot":"","sources":["../src/diagnostic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqDH,SAAS,iBAAiB,CAAC,CAAQ;IACjC,MAAM,IAAI,KAAK,CAAC,8BAA+B,CAAsB,CAAC,IAAI,EAAE,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,QAAQ,CAAC,CAAa;IAC7B,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,uBAAuB;YAC1B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvF,KAAK,wBAAwB;YAC3B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACtF,KAAK,2BAA2B;YAC9B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAClD,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QACrD,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QACrD;YACE,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAID,MAAM,UAAU,yBAAyB;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,OAAO;QACL,IAAI,CAAC,CAAC;YACJ,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,KAAK;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACnB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"diagnostic.js","sourceRoot":"","sources":["../src/diagnostic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgFH,SAAS,iBAAiB,CAAC,CAAQ;IACjC,MAAM,IAAI,KAAK,CAAC,8BAA+B,CAAsB,CAAC,IAAI,EAAE,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,QAAQ,CAAC,CAAa;IAC7B,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,uBAAuB;YAC1B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvF,KAAK,wBAAwB;YAC3B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACtF,KAAK,2BAA2B;YAC9B,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QAClD,KAAK,mBAAmB;YACtB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC;QAC5E,KAAK,sBAAsB;YACzB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,KAAK,qBAAqB;YACxB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QACjD,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QACrD,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QACrD;YACE,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAID,MAAM,UAAU,yBAAyB;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,OAAO;QACL,IAAI,CAAC,CAAC;YACJ,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,KAAK;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACnB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -4,11 +4,13 @@ export { validateManifest } from "./validate.js";
4
4
  export type { ValidationResult } from "./validate.js";
5
5
  export { manifestJsonSchema } from "./schema.js";
6
6
  export { ManifestBuilder } from "./builder.js";
7
- export type { TagName, WrapperId, ImportRole, ImportEntry, IndexedComponent, Loc, WarningCode, Warning, ScanConfig, PropUsage, OccurrenceVia, Occurrence, WrapperDef, FileImport, ParseResult, ParseContext, ParserPlugin, ScanOutput, ManifestRef, ScanStats, DetectorId, LocalDefinition, LocalDefinitionIndex, ResolveLazyManifest, LookupByTag, LazyResolverHit, CemIndex, CemIndexEntry, } from "./scan-types.js";
7
+ export type { TagName, WrapperId, ImportRole, ImportEntry, IndexedComponent, Loc, WarningCode, Warning, ScanConfig, PropUsage, OccurrenceVia, Occurrence, WrapperDef, FileImport, ParseResult, ParseContext, ParserPlugin, ScanOutput, ManifestRef, ScanStats, DetectorId, LocalDefinition, LocalDefinitionIndex, ResolveLazyManifest, ResolveLocalExport, LookupByTag, LazyResolverHit, CemIndex, CemIndexEntry, } from "./scan-types.js";
8
8
  export { asTagName, asWrapperId, emptyLocalIndex } from "./scan-types.js";
9
9
  export type { Enrichment, AuthoredLeafPayload, DerivedLeafPayload } from "./enrichment.js";
10
+ export { normalizeOwnerPath } from "./owner-utils.js";
10
11
  export { computeOccurrenceId } from "./occurrence-id.js";
11
12
  export type { CompositionRollup } from "./output-types.js";
12
13
  export * from "./output-types.js";
13
14
  export type { Diagnostic, DiagnosticSeverity, DiagnosticCollector, DiagnosticCode } from "./diagnostic.js";
14
15
  export { createDiagnosticCollector } from "./diagnostic.js";
16
+ export type { ResolveImport } from "./resolve-import-type.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { validateManifest } from "./validate.js";
3
3
  export { manifestJsonSchema } from "./schema.js";
4
4
  export { ManifestBuilder } from "./builder.js";
5
5
  export { asTagName, asWrapperId, emptyLocalIndex } from "./scan-types.js";
6
+ export { normalizeOwnerPath } from "./owner-utils.js";
6
7
  export { computeOccurrenceId } from "./occurrence-id.js";
7
8
  export * from "./output-types.js";
8
9
  export { createDiagnosticCollector } from "./diagnostic.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAe/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAI1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAe/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAI1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,11 +1,22 @@
1
1
  /**
2
- * Stable per-occurrence id keyed on (componentId, filePath, line, column).
2
+ * Stable per-occurrence id keyed on (componentId, filePath, line, column,
3
+ * ownerComponentId?).
3
4
  *
4
- * Inputs use the FINAL artifact componentId string (the hashed form that
5
- * appears on `OutputOccurrence.componentId`), so downstream consumers can
6
- * re-derive: `id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column)`.
5
+ * Inputs use the FINAL artifact componentId strings (the hashed forms that
6
+ * appear on `OutputOccurrence.componentId` / `OutputOccurrence.ownerComponentId`),
7
+ * so downstream consumers can re-derive:
8
+ * id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column, o.ownerComponentId)
7
9
  *
8
- * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound collision
9
- * at ~4 billion occurrences; full hash is overkill for current scale.
10
+ * `ownerComponentId` participates in the hash so that one JSX call site
11
+ * fanning out to multiple owner resolutions (helper-call fanout see
12
+ * docs/superpowers/specs/2026-05-22-phantom-owner-attribution-design.md)
13
+ * produces distinct ids per resolution. When `ownerComponentId` is
14
+ * undefined (parser-direct occurrence with no owner, or root JSX), the
15
+ * hash is computed without an owner segment — keeps the same shape as
16
+ * pre-fanout occurrences.
17
+ *
18
+ * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound
19
+ * collision at ~4 billion occurrences; full hash is overkill for current
20
+ * scale.
10
21
  */
11
- export declare function computeOccurrenceId(componentId: string, filePath: string, line: number, column: number): string;
22
+ export declare function computeOccurrenceId(componentId: string, filePath: string, line: number, column: number, ownerComponentId?: string): string;
@@ -1,18 +1,28 @@
1
1
  import { createHash } from "node:crypto";
2
2
  /**
3
- * Stable per-occurrence id keyed on (componentId, filePath, line, column).
3
+ * Stable per-occurrence id keyed on (componentId, filePath, line, column,
4
+ * ownerComponentId?).
4
5
  *
5
- * Inputs use the FINAL artifact componentId string (the hashed form that
6
- * appears on `OutputOccurrence.componentId`), so downstream consumers can
7
- * re-derive: `id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column)`.
6
+ * Inputs use the FINAL artifact componentId strings (the hashed forms that
7
+ * appear on `OutputOccurrence.componentId` / `OutputOccurrence.ownerComponentId`),
8
+ * so downstream consumers can re-derive:
9
+ * id === computeOccurrenceId(o.componentId, o.filePath, o.line, o.column, o.ownerComponentId)
8
10
  *
9
- * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound collision
10
- * at ~4 billion occurrences; full hash is overkill for current scale.
11
+ * `ownerComponentId` participates in the hash so that one JSX call site
12
+ * fanning out to multiple owner resolutions (helper-call fanout see
13
+ * docs/superpowers/specs/2026-05-22-phantom-owner-attribution-design.md)
14
+ * produces distinct ids per resolution. When `ownerComponentId` is
15
+ * undefined (parser-direct occurrence with no owner, or root JSX), the
16
+ * hash is computed without an owner segment — keeps the same shape as
17
+ * pre-fanout occurrences.
18
+ *
19
+ * 64 bits of entropy via 16-char SHA-256 truncation. Birthday-bound
20
+ * collision at ~4 billion occurrences; full hash is overkill for current
21
+ * scale.
11
22
  */
12
- export function computeOccurrenceId(componentId, filePath, line, column) {
13
- return createHash("sha256")
14
- .update(`${componentId}|${filePath}|${line}|${column}`)
15
- .digest("hex")
16
- .slice(0, 16);
23
+ export function computeOccurrenceId(componentId, filePath, line, column, ownerComponentId) {
24
+ const base = `${componentId}|${filePath}|${line}|${column}`;
25
+ const key = ownerComponentId !== undefined ? `${base}|${ownerComponentId}` : base;
26
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
17
27
  }
18
28
  //# sourceMappingURL=occurrence-id.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"occurrence-id.js","sourceRoot":"","sources":["../src/occurrence-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,MAAc;IAEd,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,GAAG,WAAW,IAAI,QAAQ,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;SACtD,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"occurrence-id.js","sourceRoot":"","sources":["../src/occurrence-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,gBAAyB;IAEzB,MAAM,IAAI,GAAG,GAAG,WAAW,IAAI,QAAQ,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;IAC5D,MAAM,GAAG,GAAG,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,gBAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAClF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC"}
@@ -11,32 +11,35 @@ export type OutputIdentity = {
11
11
  scope: ComponentScope;
12
12
  kind: ComponentKindOutput;
13
13
  /**
14
- * For ds + external lanes: the npm package name as resolved by the manifest layer.
14
+ * For external lane: the npm package name as resolved by the manifest layer.
15
15
  * For local lane: the owning workspace package's name when the file resides inside
16
16
  * one. Absent for root-level local files (non-workspace single-package repos).
17
17
  */
18
18
  packageName?: string;
19
19
  /**
20
- * For resolver-driven external identities: the file Node's import resolution
21
- * (and any further chain walking) landed on, relative to the leaf package
22
- * root, POSIX-normalised. Disambiguates distinct components reached via
23
- * different subpaths of the same package. Absent for byTag-driven external
24
- * identities (which disambiguate via `tagName` alone) and for local identities
25
- * (which use `filePath` instead).
20
+ * For external identities reached via a subpath import: the subpath after
21
+ * the package name (e.g. `@shoelace-style/shoelace/dist/components/button`
22
+ * → `"dist/components/button"`), POSIX-normalised. Disambiguates distinct
23
+ * components reached via different subpaths of the same package.
24
+ * - Populated string: import specifier carried a subpath.
25
+ * - Absent (`undefined`): not applicable — bare package specifier (e.g.
26
+ * `react`), byTag-driven external identities, or local identities.
27
+ * Derived from the specifier string alone — does not depend on whether
28
+ * the moduleResolver succeeded. Independent canonical signal.
26
29
  */
27
30
  modulePath?: string;
28
31
  /** For react-component / vue-component. */
29
32
  exportName?: string;
30
33
  /** For custom-element. */
31
34
  tagName?: string;
32
- /** Present for local; absent for ds/external. */
35
+ /** Present for local; absent for external. */
33
36
  repoId?: string;
34
- /** Present for local; absent for ds/external. Repo-relative path of the defining file. */
37
+ /** Present for local; absent for external. Repo-relative path of the defining file. */
35
38
  filePath?: string;
36
39
  /**
37
40
  * Source position of the local-component declaration (the function/class
38
41
  * keyword for declarations; the variable identifier for `const Foo = ...`).
39
- * Only set when `scope === "local"`. Absent for ds / external / wc.
42
+ * Only set when `scope === "local"`. Absent for external / custom-element.
40
43
  */
41
44
  definition?: {
42
45
  line: number;
@@ -85,15 +88,14 @@ export type DerivedManifestData = {
85
88
  slots?: Record<string, SlotDecl>;
86
89
  invocable?: Record<string, InvocableDecl>;
87
90
  };
88
- export type DynamicKind = "spread" | "expr" | "identifier";
89
91
  /**
90
92
  * Per-occurrence prop value:
91
93
  * - a literal (string/number/boolean/null) when the source value is statically known,
92
- * - a `{ __dynamic }` marker when the value is a non-literal expression,
94
+ * - a `{ __dynamic: true }` marker when the value is a non-literal expression,
93
95
  * - or absent (key omitted from the map) when the prop was not set on the occurrence.
94
96
  */
95
97
  export type PropValueState = string | number | boolean | null | {
96
- __dynamic: DynamicKind;
98
+ __dynamic: true;
97
99
  };
98
100
  export type PropDistribution = {
99
101
  /** Stringified literal value -> occurrence count. */
@@ -143,7 +145,12 @@ export type OutputOccurrence = {
143
145
  filePath: string;
144
146
  line: number;
145
147
  column: number;
148
+ /** Outer-most access pattern (alias for viaChain[0]). */
146
149
  via: OccurrenceVia;
150
+ /** Full provenance chain from JSX site to resolved identity, outermost-first.
151
+ * Single-element for direct imports; multi-element for HOC chains, lazy boundaries
152
+ * through barrels, etc. */
153
+ viaChain: OccurrenceVia[];
147
154
  props: Record<string, PropValueState>;
148
155
  /** Phase 2: stable id keyed on (componentId, filePath, line, column). */
149
156
  occurrenceId: string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Normalize a file path to the form used as keys in `LocalDefinitionIndex.byPath`.
3
+ *
4
+ * Parsers receive absolute paths from oxc; the local-index stores
5
+ * repo-root-relative POSIX paths. Shared helper to prevent path-normalisation
6
+ * drift in owner-resolution code paths across parsers.
7
+ *
8
+ * No-op for already-relative paths beyond backslash → forward-slash conversion.
9
+ */
10
+ export declare function normalizeOwnerPath(file: string, repoRoot?: string): string;
@@ -0,0 +1,16 @@
1
+ import { relative, isAbsolute } from "node:path";
2
+ /**
3
+ * Normalize a file path to the form used as keys in `LocalDefinitionIndex.byPath`.
4
+ *
5
+ * Parsers receive absolute paths from oxc; the local-index stores
6
+ * repo-root-relative POSIX paths. Shared helper to prevent path-normalisation
7
+ * drift in owner-resolution code paths across parsers.
8
+ *
9
+ * No-op for already-relative paths beyond backslash → forward-slash conversion.
10
+ */
11
+ export function normalizeOwnerPath(file, repoRoot) {
12
+ if (!repoRoot || !isAbsolute(file))
13
+ return file.replace(/\\/g, "/");
14
+ return relative(repoRoot, file).replace(/\\/g, "/");
15
+ }
16
+ //# sourceMappingURL=owner-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-utils.js","sourceRoot":"","sources":["../src/owner-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,QAAiB;IAChE,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpE,OAAO,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Function type for "where does this import specifier point?" — the single
3
+ * source of truth consumed by both the lazy resolver and the
4
+ * classifyImportOrigin helper. Implementation lives in
5
+ * `cli/src/walker/resolve-import.ts` (knows about node-resolve quirks,
6
+ * tsconfig paths, config aliases). Type lives here so plugin-core consumers
7
+ * (ast-utils, parsers) can depend on it without depending on the CLI.
8
+ *
9
+ * Returns the absolute file path the specifier resolves to, or null if no
10
+ * resolution layer matches.
11
+ *
12
+ * See ADR 0001 for the dual-role rationale and CONTEXT.md "ResolveImport"
13
+ * entry for the glossary definition.
14
+ */
15
+ export type ResolveImport = (from: string, spec: string) => string | null;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolve-import-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-import-type.js","sourceRoot":"","sources":["../src/resolve-import-type.ts"],"names":[],"mappings":""}
@@ -63,6 +63,20 @@ export type LazyResolverHit = {
63
63
  matched: IndexedComponent | null;
64
64
  };
65
65
  export type ResolveLazyManifest = (fromFile: string, specifier: string, exportName: string) => LazyResolverHit | null;
66
+ /**
67
+ * File-path-anchored local barrel walker. Given a known-local file and an
68
+ * export name, walks `export { X } from "./Y"` / `export * from "./Y"` chains
69
+ * to the terminal file the chain ended at. Returns null on cross-package
70
+ * boundary, cycle, max-hop overflow, or unresolvable re-export specifier.
71
+ * The terminal `exportName` may differ from the input when the chain renames
72
+ * (`export { X as Y } from "./Z"`); caller does the final byPath lookup to
73
+ * confirm the file actually exports the returned name. Backed by the lazy
74
+ * resolver's `parsedBarrel` cache.
75
+ */
76
+ export type ResolveLocalExport = (filePath: string, exportName: string) => {
77
+ filePath: string;
78
+ exportName: string;
79
+ } | null;
66
80
  export type LookupByTag = (tagName: string) => Extract<IndexedComponent, {
67
81
  kind: "custom-element";
68
82
  }> | null;
@@ -124,14 +138,6 @@ export type PropUsage = {
124
138
  name: string;
125
139
  isDynamic: boolean;
126
140
  literalValue?: string | number | boolean;
127
- /**
128
- * When `isDynamic` is true, distinguishes the kind of dynamic value:
129
- * - "spread" — JSX spread attribute (`{...rest}`); name is `"...rest"`.
130
- * - "identifier" — bare identifier reference (`prop={someVar}`).
131
- * - "expr" — any other expression (`prop={a + b}`, calls, ternaries, etc.).
132
- * Omitted when `isDynamic` is false.
133
- */
134
- dynamicKind?: "spread" | "expr" | "identifier";
135
141
  };
136
142
  export type { OccurrenceVia } from "./via-types.js";
137
143
  export type Occurrence = {
@@ -157,10 +163,28 @@ export type Occurrence = {
157
163
  */
158
164
  depth?: number;
159
165
  /**
160
- * ComponentId of the enclosing component-definition. Resolved by parsers
161
- * via ancestor walk. The CLI projection layer hashes this into
162
- * `OutputOccurrence.ownerComponentId`. Undefined when no enclosing tracked
163
- * component was found.
166
+ * Identity of the Component definition that owns this Occurrence (i.e.
167
+ * lexically encloses it in source). Resolved by parsers via ancestor walk.
168
+ * The CLI projection layer (Assemble) hashes this into
169
+ * `OutputOccurrence.ownerComponentId` and drives the composition rollup.
170
+ *
171
+ * Convention for parsers that emit `ownerComponentId`:
172
+ * - Absent (undefined) ⇒ Occurrence is a "root" — no enclosing Component
173
+ * owns it. Composition rollup counts these in `isRootCount`.
174
+ * - Present ⇒ MUST be an Identity that matches a Local definition produced
175
+ * by the corresponding `detect-*.ts` for that file. Mismatch causes the
176
+ * composition rollup's id-equality check to drop the edge silently.
177
+ * - Parsers MUST NOT emit a sentinel value (e.g. "root", null-stringified)
178
+ * in this field. Absence is the signal.
179
+ *
180
+ * Per-language framing:
181
+ * - parser-vue: owner = enclosing SFC's vue-component Identity.
182
+ * - parser-react: owner = nearest enclosing function/class Component binding.
183
+ * - parser-lit (planned, #76): owner = enclosing @customElement class Identity.
184
+ * - parser-html: no owner; HTML files are not Component definitions.
185
+ *
186
+ * Path-based identity resolution must use `normalizeOwnerPath` (from
187
+ * `@component-compass/plugin-core`) to match local-index keys.
164
188
  */
165
189
  ownerComponentId?: ComponentId;
166
190
  };
@@ -190,8 +214,16 @@ export type ParseContext = {
190
214
  config: ScanConfig;
191
215
  resolveImport: (from: string, spec: string) => string | null;
192
216
  resolveLazyManifest: ResolveLazyManifest;
217
+ resolveLocalExport: ResolveLocalExport;
193
218
  lookupByTag: LookupByTag;
194
219
  repoRoot: string;
220
+ /**
221
+ * Set of package names known to belong to the scan's repo (workspace
222
+ * siblings + declared deps). Threaded into `classifyImportOrigin` so
223
+ * specifiers that don't resolve on disk (uninstalled workspace siblings)
224
+ * can still recover external-package identity.
225
+ */
226
+ knownPackageNames?: ReadonlySet<string>;
195
227
  /** Shared diagnostic collector for the scan run. */
196
228
  collector?: DiagnosticCollector;
197
229
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"scan-types.js","sourceRoot":"","sources":["../src/scan-types.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,CAAY,CAAC;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAS,EAAa,EAAE,CAAC,CAAc,CAAC;AA2GpE,MAAM,CAAC,MAAM,eAAe,GAAyB;IACnD,MAAM,EAAE,IAAI,GAAG,EAAE;IACjB,KAAK,EAAE,IAAI,GAAG,EAAE;CACjB,CAAC"}
1
+ {"version":3,"file":"scan-types.js","sourceRoot":"","sources":["../src/scan-types.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,CAAY,CAAC;AAC9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAS,EAAa,EAAE,CAAC,CAAc,CAAC;AA0HpE,MAAM,CAAC,MAAM,eAAe,GAAyB;IACnD,MAAM,EAAE,IAAI,GAAG,EAAE;IACjB,KAAK,EAAE,IAAI,GAAG,EAAE;CACjB,CAAC"}
@@ -25,4 +25,38 @@ export type OccurrenceVia = {
25
25
  kind: "context-provider";
26
26
  } | {
27
27
  kind: "local-component";
28
+ } | {
29
+ kind: "hoc-wrapper";
30
+ hocCallee: string;
31
+ specifier: string;
32
+ import: string;
33
+ } | {
34
+ kind: "dynamic-map";
35
+ mapName: string;
36
+ mapLoc: {
37
+ file: string;
38
+ line: number;
39
+ column: number;
40
+ };
41
+ } | {
42
+ kind: "lazy-import";
43
+ wrapperCallee: string;
44
+ specifier: string;
45
+ import: string;
46
+ } | {
47
+ kind: "dynamic-binding";
48
+ bindingName: string;
49
+ bindingSource: "parameter" | "hook-return";
50
+ } | {
51
+ kind: "prop-forward";
52
+ bindingName: string;
53
+ constructionSite: {
54
+ file: string;
55
+ line: number;
56
+ column: number;
57
+ };
58
+ } | {
59
+ kind: "helper-call";
60
+ callee: string;
61
+ calleeFile: string;
28
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@component-compass/plugin-core",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,12 +21,12 @@
21
21
  "clean": "rm -rf dist .turbo"
22
22
  },
23
23
  "dependencies": {
24
- "ajv": "^8.17.0"
24
+ "ajv": "8.20.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@types/node": "^24.0.0",
28
- "typescript": "^5.9.0",
29
- "vitest": "^4.0.0"
27
+ "@types/node": "24.12.2",
28
+ "typescript": "5.9.3",
29
+ "vitest": "4.1.5"
30
30
  },
31
31
  "engines": {
32
32
  "node": ">=24"