as a hand-rolled primitive (reimplements-primitive)", () => {
const ix = preferLibraryIndex();
const alertDiv = addRawNode(ix, { element: "div", nodePath: "0:2" });
ix.add(
makeClassNameDynamicFact({
file: "src/app.tsx",
nodeId: alertDiv.id,
attr: "className",
reason: "identifier",
snippet: "styles.alert",
originPath: "0:2",
location: { file: "src/app.tsx", line: 6, column: 4 },
})
);
// `Table` is canonical, but `table` is a common layout class — the denylist
// must keep it from flagging (the heuristic's low-noise guarantee).
const tableDiv = addRawNode(ix, { element: "div", nodePath: "0:3" });
ix.add(
makeClassNameDynamicFact({
file: "src/app.tsx",
nodeId: tableDiv.id,
attr: "className",
reason: "identifier",
snippet: "styles.table",
originPath: "0:3",
location: { file: "src/app.tsx", line: 7, column: 4 },
})
);
const reimpls = ruleComponentsPreferLibrary(ix).filter(
(finding) => finding.attributes?.precisionTier === "classname-reimpl"
);
expect(reimpls).toHaveLength(1);
expect(reimpls[0].attributes?.suggestedComponent).toBe("Alert");
expect(reimpls[0].attributes?.matchedClass).toBe("alert");
expect(reimpls[0].attributes?.advisory).toBe(true);
// Advisory: capped at warn (never a hard error/block) and no auto-fix.
expect(reimpls[0].level).toBe("warn");
expect(reimpls[0].fix).toBeUndefined();
});
it("does not flag imported bespoke components from bare package canonical sources without export inventory", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [{ kind: "npm", specifier: "@acme/ui" }],
},
},
},
})
);
const node = makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:0",
element: "Card",
location: { file: "src/app.tsx", line: 5, column: 4 },
});
ix.add(node);
ix.add(
makeUsageImportFact({
file: "src/app.tsx",
local: "Card",
imported: "Card",
source: "../components/Card",
location: { file: "src/app.tsx", line: 1, column: 0 },
})
);
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
});
it("flags imported bespoke components when package canonical sources include the exported component", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [{ kind: "npm", specifier: "@acme/ui", include: ["Card"] }],
},
},
},
})
);
const node = makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:0",
element: "Card",
location: { file: "src/app.tsx", line: 5, column: 4 },
});
ix.add(node);
ix.add(
makeUsageImportFact({
file: "src/app.tsx",
local: "Card",
imported: "Card",
source: "../components/Card",
location: { file: "src/app.tsx", line: 1, column: 0 },
})
);
const findings = ruleComponentsPreferLibrary(ix);
expect(findings).toHaveLength(1);
expect(findings[0]).toMatchObject({
ruleId: "components/prefer-library",
code: "FUI1004",
attributes: {
suggestedComponent: "Card",
suggestedImport: "@acme/ui",
},
});
});
it("does not flag bespoke components from bare registry canonical sources without export inventory", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [
{
kind: "registry",
registryId: "fragments-ui",
receiptPath: ".fragments/registry-lock.json",
installPath: "src/fragments/ui",
importPath: "@/fragments/ui",
},
],
},
},
},
})
);
const node = makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:0",
element: "Card",
location: { file: "src/app.tsx", line: 5, column: 4 },
});
ix.add(node);
ix.add(
makeUsageImportFact({
file: "src/app.tsx",
local: "Card",
imported: "Card",
source: "../components/Card",
location: { file: "src/app.tsx", line: 1, column: 1 },
})
);
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
});
it("flags bespoke components when registry canonical sources include the exported component", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [
{
kind: "registry",
registryId: "fragments-ui",
receiptPath: ".fragments/registry-lock.json",
installPath: "src/fragments/ui",
importPath: "@/fragments/ui",
include: ["Card"],
},
],
},
},
},
})
);
const node = makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:0",
element: "Card",
location: { file: "src/app.tsx", line: 5, column: 4 },
});
ix.add(node);
ix.add(
makeUsageImportFact({
file: "src/app.tsx",
local: "Card",
imported: "Card",
source: "../components/Card",
location: { file: "src/app.tsx", line: 1, column: 1 },
})
);
const findings = ruleComponentsPreferLibrary(ix);
expect(findings).toHaveLength(1);
expect(findings[0]).toMatchObject({
ruleId: "components/prefer-library",
attributes: {
suggestedComponent: "Card",
suggestedImport: "@/fragments/ui",
suggestedImportSourceKind: "registry",
},
});
});
it("respects canonical source include lists", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [
{
kind: "npm",
specifier: "@acme/ui",
include: ["Button"],
},
],
},
},
},
})
);
addRawNode(ix, {
element: "button",
nodePath: "0:0",
text: "Save",
interactive: true,
});
const node = makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:1",
element: "MarketingCard",
location: { file: "src/app.tsx", line: 8, column: 4 },
});
ix.add(node);
ix.add(
makeUsageImportFact({
file: "src/app.tsx",
local: "MarketingCard",
imported: "MarketingCard",
source: "../components/MarketingCard",
location: { file: "src/app.tsx", line: 2, column: 0 },
})
);
const findings = ruleComponentsPreferLibrary(ix);
expect(findings).toHaveLength(1);
expect(findings[0]).toMatchObject({
ruleId: "components/prefer-library",
attributes: {
rawValue: "button",
suggestedComponent: "Button",
suggestedImport: "@acme/ui",
},
});
});
it("does not flag raw primitives inside a canonical source implementation", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [
{
kind: "npm",
specifier: "@fragments-sdk/ui",
implementationPath: "src/fragments/ui",
include: ["Button"],
},
],
},
},
},
})
);
ix.add(
makeUsageNodeFact({
file: "src/fragments/ui/components/Button/index.tsx",
nodePath: "0:0",
element: "button",
interactive: true,
location: {
file: "src/fragments/ui/components/Button/index.tsx",
line: 5,
column: 4,
},
})
);
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
});
it("matches canonical imports within the current file only", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalSources: [
{
kind: "npm",
specifier: "@fragments-sdk/ui",
implementationPath: "src/fragments/ui",
include: ["Table"],
},
],
},
},
},
})
);
ix.add(
makeUsageNodeFact({
file: "src/page/settings.tsx",
nodePath: "0:0",
element: "Table",
location: { file: "src/page/settings.tsx", line: 5, column: 4 },
})
);
ix.add(
makeUsageImportFact({
file: "src/page/settings.tsx",
local: "Table",
imported: "Table",
source: "@fragments-sdk/ui",
location: { file: "src/page/settings.tsx", line: 1, column: 0 },
})
);
ix.add(
makeUsageImportFact({
file: "src/fragments/ui/components/DataTable/index.tsx",
local: "Table",
imported: "Table",
source: "../Table",
location: {
file: "src/fragments/ui/components/DataTable/index.tsx",
line: 1,
column: 0,
},
})
);
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
});
it("attaches a deterministic component fix from confirmed canonical mappings", () => {
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalMappings: [
{
name: "Button",
canonical: "Button",
htmlEquivalent: "button",
importPath: "@acme/ui",
propMapping: [{ rawProp: "className", canonicalProp: "variant" }],
confidence: 0.94,
},
],
},
},
},
})
);
ix.add(
makeUsageNodeFact({
file: "src/app.tsx",
nodePath: "0:0",
element: "button",
location: { file: "src/app.tsx", line: 5, column: 4 },
})
);
const findings = ruleComponentsPreferLibrary(ix);
expect(findings).toHaveLength(1);
expect(findings[0]).toMatchObject({
ruleId: "components/prefer-library",
fix: {
kind: "replaceComponent",
from: "button",
to: "Button",
deterministic: true,
},
attributes: {
suggestedComponent: "Button",
suggestedImport: "@acme/ui",
propMapping: [{ rawProp: "className", canonicalProp: "variant" }],
},
});
});
it("does not flag generic
/
from an inferred component primitive (anti-flood)", () => {
// A library component built on a (Accordion) or
(Badge) must
// NOT turn every container into a bespoke re-implementation of it. This was
// the 10k-finding canonical-usage flood: div → Accordion, span → Badge.
const ix = new FactIndex();
ix.addMany(
compileGlobalGovernanceFacts({
rules: {
"components/prefer-library": {
enabled: true,
severity: "warning",
options: {
canonicalMappings: [
{
name: "Accordion",
canonical: "Accordion",
htmlEquivalent: "div",
importPath: "@acme/ui",
},
{
name: "Badge",
canonical: "Badge",
htmlEquivalent: "span",
importPath: "@acme/ui",
},
],
},
},
},
})
);
addRawNode(ix, { element: "div", nodePath: "0:0" });
addRawNode(ix, { element: "span", nodePath: "0:1" });
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(0);
});
it("suppresses