From bb110889147b511257902a4a044b2f8e913b60d0 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sat, 21 Feb 2026 19:41:30 +0530 Subject: [PATCH 1/9] feat: add DRC constraint to prevent I2C SDA and SCL from connecting --- index.ts | 1 + lib/check-invalid-pin-connections.ts | 58 +++++++++++++++++++ lib/run-all-checks.ts | 2 + package.json | 13 +++-- .../lib/check-invalid-pin-connections.test.ts | 58 +++++++++++++++++++ 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 lib/check-invalid-pin-connections.ts create mode 100644 tests/lib/check-invalid-pin-connections.test.ts diff --git a/index.ts b/index.ts index 8fca69a..62b31e2 100644 --- a/index.ts +++ b/index.ts @@ -9,4 +9,5 @@ export { checkSourceTracesHavePcbTraces } from "./lib/check-source-traces-have-p export { checkPcbTracesOutOfBoard } from "./lib/check-trace-out-of-board/checkTraceOutOfBoard" export { checkPcbComponentOverlap } from "./lib/check-pcb-components-overlap/checkPcbComponentOverlap" export { checkPinMustBeConnected } from "./lib/check-pin-must-be-connected" +export { checkInvalidPinConnections } from "./lib/check-invalid-pin-connections" export { runAllChecks } from "./lib/run-all-checks" diff --git a/lib/check-invalid-pin-connections.ts b/lib/check-invalid-pin-connections.ts new file mode 100644 index 0000000..2cf8e33 --- /dev/null +++ b/lib/check-invalid-pin-connections.ts @@ -0,0 +1,58 @@ +import type { AnyCircuitElement } from "circuit-json" +import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" + +export type InvalidPinConnectionError = { + type: "invalid_pin_connection_error" + invalid_pin_connection_error_id: string + error_type: "invalid_pin_connection_error" + message: string + source_port_ids: string[] +} + +export function checkInvalidPinConnections( + circuitJson: AnyCircuitElement[], +): InvalidPinConnectionError[] { + const errors: InvalidPinConnectionError[] = [] + + // Get all source ports to easily look up their attributes + const sourcePorts = circuitJson.filter( + (el): el is Extract => + el.type === "source_port", + ) + const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) + + const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) + + for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { + let hasSda = false + let hasScl = false + + const conflictingPortIds: string[] = [] + + for (const portId of connectedPortIds) { + const port = portMap.get(portId) + if (!port) continue + + if (port.is_configured_for_i2c_sda) { + hasSda = true + conflictingPortIds.push(portId) + } + if (port.is_configured_for_i2c_scl) { + hasScl = true + conflictingPortIds.push(portId) + } + } + + if (hasSda && hasScl) { + errors.push({ + type: "invalid_pin_connection_error", + invalid_pin_connection_error_id: `invalid_pin_connection_error_${netId}`, + error_type: "invalid_pin_connection_error", + message: "I2C SDA and SCL pins are connected together", + source_port_ids: conflictingPortIds, + }) + } + } + + return errors +} diff --git a/lib/run-all-checks.ts b/lib/run-all-checks.ts index d98a46b..baae606 100644 --- a/lib/run-all-checks.ts +++ b/lib/run-all-checks.ts @@ -9,6 +9,7 @@ import { checkSourceTracesHavePcbTraces } from "./check-source-traces-have-pcb-t import { checkPcbTracesOutOfBoard } from "./check-trace-out-of-board/checkTraceOutOfBoard" import { checkPcbComponentOverlap } from "./check-pcb-components-overlap/checkPcbComponentOverlap" import { checkPinMustBeConnected } from "./check-pin-must-be-connected" +import { checkInvalidPinConnections } from "./check-invalid-pin-connections" import type { AnyCircuitElement } from "circuit-json" export async function runAllChecks(circuitJson: AnyCircuitElement[]) { @@ -24,5 +25,6 @@ export async function runAllChecks(circuitJson: AnyCircuitElement[]) { ...checkPcbTracesOutOfBoard(circuitJson), ...checkPcbComponentOverlap(circuitJson), ...checkPinMustBeConnected(circuitJson), + ...checkInvalidPinConnections(circuitJson), ] } diff --git a/package.json b/package.json index 08e9ad1..e9b3980 100644 --- a/package.json +++ b/package.json @@ -19,18 +19,19 @@ "@types/bun": "^1.2.8", "@types/debug": "^4.1.12", "bun-match-svg": "^0.0.11", + "circuit-json": "^0.0.386", + "circuit-json-to-connectivity-map": "^0.0.23", "circuit-to-svg": "^0.0.166", - "circuit-json": "^0.0.380", "debug": "^4.3.5", "tscircuit": "^0.0.525", - "zod": "^3.23.8", - "tsup": "^8.2.3" + "tsup": "^8.2.3", + "zod": "^3.23.8" }, "peerDependencies": { - "circuit-json": "*", - "@tscircuit/math-utils": "*", - "circuit-json-to-connectivity-map": "*", "@flatten-js/core": "*", + "@tscircuit/math-utils": "*", + "circuit-json": "*", + "circuit-json-to-connectivity-map": "^0.0.23", "typescript": "^5.5.3" } } diff --git a/tests/lib/check-invalid-pin-connections.test.ts b/tests/lib/check-invalid-pin-connections.test.ts new file mode 100644 index 0000000..fc5945b --- /dev/null +++ b/tests/lib/check-invalid-pin-connections.test.ts @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test" +import { checkInvalidPinConnections } from "../../lib/check-invalid-pin-connections" +import type { AnyCircuitElement } from "circuit-json" + +test("checkInvalidPinConnections detects SDA connected to SCL", () => { + const circuitJson: AnyCircuitElement[] = [ + { + type: "source_port", + source_port_id: "port_sda", + name: "SDA", + is_configured_for_i2c_sda: true, + }, + { + type: "source_port", + source_port_id: "port_scl", + name: "SCL", + is_configured_for_i2c_scl: true, + }, + { + type: "source_trace", + source_trace_id: "trace_1", + connected_source_port_ids: ["port_sda", "port_scl"], + connected_source_net_ids: [], + }, + ] as AnyCircuitElement[] + + const errors = checkInvalidPinConnections(circuitJson) + expect(errors).toHaveLength(1) + expect(errors[0].message).toBe("I2C SDA and SCL pins are connected together") + expect(errors[0].source_port_ids).toContain("port_sda") + expect(errors[0].source_port_ids).toContain("port_scl") +}) + +test("checkInvalidPinConnections allows SDA connected to SDA", () => { + const circuitJson: AnyCircuitElement[] = [ + { + type: "source_port", + source_port_id: "port_sda1", + name: "SDA1", + is_configured_for_i2c_sda: true, + }, + { + type: "source_port", + source_port_id: "port_sda2", + name: "SDA2", + is_configured_for_i2c_sda: true, + }, + { + type: "source_trace", + source_trace_id: "trace_1", + connected_source_port_ids: ["port_sda1", "port_sda2"], + connected_source_net_ids: [], + }, + ] as AnyCircuitElement[] + + const errors = checkInvalidPinConnections(circuitJson) + expect(errors).toHaveLength(0) +}) From 5e375a7b8e93a29519571bb49832ac81f1cde8e4 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sat, 21 Feb 2026 19:44:01 +0530 Subject: [PATCH 2/9] j --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e9b3980..ec8ba5a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@types/debug": "^4.1.12", "bun-match-svg": "^0.0.11", "circuit-json": "^0.0.386", - "circuit-json-to-connectivity-map": "^0.0.23", "circuit-to-svg": "^0.0.166", "debug": "^4.3.5", "tscircuit": "^0.0.525", @@ -29,9 +28,8 @@ }, "peerDependencies": { "@flatten-js/core": "*", - "@tscircuit/math-utils": "*", "circuit-json": "*", - "circuit-json-to-connectivity-map": "^0.0.23", + "circuit-json-to-connectivity-map": "*", "typescript": "^5.5.3" } -} +} \ No newline at end of file From fac8b375390f323bdb943215a79fccf455e8a2cc Mon Sep 17 00:00:00 2001 From: techmannih Date: Sat, 21 Feb 2026 19:45:19 +0530 Subject: [PATCH 3/9] j --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ec8ba5a..a090945 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "peerDependencies": { "@flatten-js/core": "*", "circuit-json": "*", + "@tscircuit/math-utils": "*", "circuit-json-to-connectivity-map": "*", "typescript": "^5.5.3" } From e2407e219ac8ffb58575c14859bf82bcf06f218e Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 09:21:25 +0530 Subject: [PATCH 4/9] update --- index.ts | 2 +- ...nections.ts => check-i2c-misconfigured.ts} | 25 ++++++++----------- lib/run-all-checks.ts | 4 +-- package.json | 2 +- ...est.ts => check-i2c-misconfigured.test.ts} | 12 ++++----- 5 files changed, 20 insertions(+), 25 deletions(-) rename lib/{check-invalid-pin-connections.ts => check-i2c-misconfigured.ts} (67%) rename tests/lib/{check-invalid-pin-connections.test.ts => check-i2c-misconfigured.test.ts} (75%) diff --git a/index.ts b/index.ts index 62b31e2..c259a37 100644 --- a/index.ts +++ b/index.ts @@ -9,5 +9,5 @@ export { checkSourceTracesHavePcbTraces } from "./lib/check-source-traces-have-p export { checkPcbTracesOutOfBoard } from "./lib/check-trace-out-of-board/checkTraceOutOfBoard" export { checkPcbComponentOverlap } from "./lib/check-pcb-components-overlap/checkPcbComponentOverlap" export { checkPinMustBeConnected } from "./lib/check-pin-must-be-connected" -export { checkInvalidPinConnections } from "./lib/check-invalid-pin-connections" +export { checkI2cMisconfigured } from "./lib/check-i2c-misconfigured" export { runAllChecks } from "./lib/run-all-checks" diff --git a/lib/check-invalid-pin-connections.ts b/lib/check-i2c-misconfigured.ts similarity index 67% rename from lib/check-invalid-pin-connections.ts rename to lib/check-i2c-misconfigured.ts index 2cf8e33..f81fb4d 100644 --- a/lib/check-invalid-pin-connections.ts +++ b/lib/check-i2c-misconfigured.ts @@ -1,18 +1,13 @@ -import type { AnyCircuitElement } from "circuit-json" +import type { + AnyCircuitElement, + SourceI2cMisconfiguredError, +} from "circuit-json" import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" -export type InvalidPinConnectionError = { - type: "invalid_pin_connection_error" - invalid_pin_connection_error_id: string - error_type: "invalid_pin_connection_error" - message: string - source_port_ids: string[] -} - -export function checkInvalidPinConnections( +export function checkI2cMisconfigured( circuitJson: AnyCircuitElement[], -): InvalidPinConnectionError[] { - const errors: InvalidPinConnectionError[] = [] +): SourceI2cMisconfiguredError[] { + const errors: SourceI2cMisconfiguredError[] = [] // Get all source ports to easily look up their attributes const sourcePorts = circuitJson.filter( @@ -45,9 +40,9 @@ export function checkInvalidPinConnections( if (hasSda && hasScl) { errors.push({ - type: "invalid_pin_connection_error", - invalid_pin_connection_error_id: `invalid_pin_connection_error_${netId}`, - error_type: "invalid_pin_connection_error", + type: "source_i2c_misconfigured_error", + source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${netId}`, + error_type: "source_i2c_misconfigured_error", message: "I2C SDA and SCL pins are connected together", source_port_ids: conflictingPortIds, }) diff --git a/lib/run-all-checks.ts b/lib/run-all-checks.ts index baae606..090fa39 100644 --- a/lib/run-all-checks.ts +++ b/lib/run-all-checks.ts @@ -9,7 +9,7 @@ import { checkSourceTracesHavePcbTraces } from "./check-source-traces-have-pcb-t import { checkPcbTracesOutOfBoard } from "./check-trace-out-of-board/checkTraceOutOfBoard" import { checkPcbComponentOverlap } from "./check-pcb-components-overlap/checkPcbComponentOverlap" import { checkPinMustBeConnected } from "./check-pin-must-be-connected" -import { checkInvalidPinConnections } from "./check-invalid-pin-connections" +import { checkI2cMisconfigured } from "./check-i2c-misconfigured" import type { AnyCircuitElement } from "circuit-json" export async function runAllChecks(circuitJson: AnyCircuitElement[]) { @@ -25,6 +25,6 @@ export async function runAllChecks(circuitJson: AnyCircuitElement[]) { ...checkPcbTracesOutOfBoard(circuitJson), ...checkPcbComponentOverlap(circuitJson), ...checkPinMustBeConnected(circuitJson), - ...checkInvalidPinConnections(circuitJson), + ...checkI2cMisconfigured(circuitJson), ] } diff --git a/package.json b/package.json index a090945..48eb8db 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@types/bun": "^1.2.8", "@types/debug": "^4.1.12", "bun-match-svg": "^0.0.11", - "circuit-json": "^0.0.386", + "circuit-json": "^0.0.387", "circuit-to-svg": "^0.0.166", "debug": "^4.3.5", "tscircuit": "^0.0.525", diff --git a/tests/lib/check-invalid-pin-connections.test.ts b/tests/lib/check-i2c-misconfigured.test.ts similarity index 75% rename from tests/lib/check-invalid-pin-connections.test.ts rename to tests/lib/check-i2c-misconfigured.test.ts index fc5945b..d05fc92 100644 --- a/tests/lib/check-invalid-pin-connections.test.ts +++ b/tests/lib/check-i2c-misconfigured.test.ts @@ -1,8 +1,8 @@ import { expect, test } from "bun:test" -import { checkInvalidPinConnections } from "../../lib/check-invalid-pin-connections" +import { checkI2cMisconfigured } from "../../lib/check-i2c-misconfigured" import type { AnyCircuitElement } from "circuit-json" -test("checkInvalidPinConnections detects SDA connected to SCL", () => { +test("checkI2cMisconfigured detects SDA connected to SCL", () => { const circuitJson: AnyCircuitElement[] = [ { type: "source_port", @@ -24,14 +24,14 @@ test("checkInvalidPinConnections detects SDA connected to SCL", () => { }, ] as AnyCircuitElement[] - const errors = checkInvalidPinConnections(circuitJson) + const errors = checkI2cMisconfigured(circuitJson) expect(errors).toHaveLength(1) - expect(errors[0].message).toBe("I2C SDA and SCL pins are connected together") + expect(errors[0].message).toEqual(expect.stringContaining("I2C")) expect(errors[0].source_port_ids).toContain("port_sda") expect(errors[0].source_port_ids).toContain("port_scl") }) -test("checkInvalidPinConnections allows SDA connected to SDA", () => { +test("checkI2cMisconfigured allows SDA connected to SDA", () => { const circuitJson: AnyCircuitElement[] = [ { type: "source_port", @@ -53,6 +53,6 @@ test("checkInvalidPinConnections allows SDA connected to SDA", () => { }, ] as AnyCircuitElement[] - const errors = checkInvalidPinConnections(circuitJson) + const errors = checkI2cMisconfigured(circuitJson) expect(errors).toHaveLength(0) }) From a588b6890c8dae827b52c82b5ecf79416ea4e051 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 10:09:50 +0530 Subject: [PATCH 5/9] Rename function to checkI2cSdaConnectedToSclMisconfigured and improve error message --- index.ts | 2 +- lib/check-i2c-misconfigured.ts | 53 ------------- lib/check-i2c-sda-connected-to-scl.ts | 78 +++++++++++++++++++ lib/run-all-checks.ts | 4 +- ...=> check-i2c-sda-connected-to-scl.test.ts} | 25 ++++-- 5 files changed, 100 insertions(+), 62 deletions(-) delete mode 100644 lib/check-i2c-misconfigured.ts create mode 100644 lib/check-i2c-sda-connected-to-scl.ts rename tests/lib/{check-i2c-misconfigured.test.ts => check-i2c-sda-connected-to-scl.test.ts} (61%) diff --git a/index.ts b/index.ts index c259a37..380688c 100644 --- a/index.ts +++ b/index.ts @@ -9,5 +9,5 @@ export { checkSourceTracesHavePcbTraces } from "./lib/check-source-traces-have-p export { checkPcbTracesOutOfBoard } from "./lib/check-trace-out-of-board/checkTraceOutOfBoard" export { checkPcbComponentOverlap } from "./lib/check-pcb-components-overlap/checkPcbComponentOverlap" export { checkPinMustBeConnected } from "./lib/check-pin-must-be-connected" -export { checkI2cMisconfigured } from "./lib/check-i2c-misconfigured" +export { checkI2cSdaConnectedToSclMisconfigured } from "./lib/check-i2c-sda-connected-to-scl" export { runAllChecks } from "./lib/run-all-checks" diff --git a/lib/check-i2c-misconfigured.ts b/lib/check-i2c-misconfigured.ts deleted file mode 100644 index f81fb4d..0000000 --- a/lib/check-i2c-misconfigured.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { - AnyCircuitElement, - SourceI2cMisconfiguredError, -} from "circuit-json" -import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" - -export function checkI2cMisconfigured( - circuitJson: AnyCircuitElement[], -): SourceI2cMisconfiguredError[] { - const errors: SourceI2cMisconfiguredError[] = [] - - // Get all source ports to easily look up their attributes - const sourcePorts = circuitJson.filter( - (el): el is Extract => - el.type === "source_port", - ) - const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) - - const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) - - for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { - let hasSda = false - let hasScl = false - - const conflictingPortIds: string[] = [] - - for (const portId of connectedPortIds) { - const port = portMap.get(portId) - if (!port) continue - - if (port.is_configured_for_i2c_sda) { - hasSda = true - conflictingPortIds.push(portId) - } - if (port.is_configured_for_i2c_scl) { - hasScl = true - conflictingPortIds.push(portId) - } - } - - if (hasSda && hasScl) { - errors.push({ - type: "source_i2c_misconfigured_error", - source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${netId}`, - error_type: "source_i2c_misconfigured_error", - message: "I2C SDA and SCL pins are connected together", - source_port_ids: conflictingPortIds, - }) - } - } - - return errors -} diff --git a/lib/check-i2c-sda-connected-to-scl.ts b/lib/check-i2c-sda-connected-to-scl.ts new file mode 100644 index 0000000..5dd7264 --- /dev/null +++ b/lib/check-i2c-sda-connected-to-scl.ts @@ -0,0 +1,78 @@ +import type { + AnyCircuitElement, + SourceI2cMisconfiguredError, +} from "circuit-json" +import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" + +type SourceComponent = Extract< + AnyCircuitElement, + { source_component_id: string; name: string } +> + +export function checkI2cSdaConnectedToSclMisconfigured( + circuitJson: AnyCircuitElement[], +): SourceI2cMisconfiguredError[] { + const errors: SourceI2cMisconfiguredError[] = [] + + const sourceComponents = circuitJson.filter( + (el): el is SourceComponent => + "source_component_id" in el && + (el.type === "source_component" || el.type.startsWith("source_simple_")), + ) + + // Get all source ports to easily look up their attributes + const sourcePorts = circuitJson.filter( + (el): el is Extract => + el.type === "source_port", + ) + const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) + + const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) + + for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { + let hasSda = false + let hasScl = false + + const conflictingPortIds: string[] = [] + + for (const portId of connectedPortIds) { + const port = portMap.get(portId) + if (!port) continue + + if (port.is_configured_for_i2c_sda) { + hasSda = true + conflictingPortIds.push(portId) + } + if (port.is_configured_for_i2c_scl) { + hasScl = true + conflictingPortIds.push(portId) + } + } + + if (hasSda && hasScl) { + // Sort conflicting port IDs to ensure deterministic ID generation + const sortedConflicts = [...conflictingPortIds].sort() + const conflictIdStr = sortedConflicts.join("_") + + const portDetails = sortedConflicts.map(portId => { + const port = portMap.get(portId) + const component = sourceComponents.find( + (c) => c.source_component_id === port?.source_component_id, + ) + const componentName = component?.name ?? "Unknown" + const portName = port?.name ?? "Unknown" + return `Port ${portName} on ${componentName}` + }) + + errors.push({ + type: "source_i2c_misconfigured_error", + source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, + error_type: "source_i2c_misconfigured_error", + message: `I2C SDA and SCL pins are connected together on the same net. To fix this, ensure SDA and SCL are routed to separate nets. Conflicting ports: ${portDetails.join(", ")}`, + source_port_ids: conflictingPortIds, + }) + } + } + + return errors +} diff --git a/lib/run-all-checks.ts b/lib/run-all-checks.ts index 090fa39..3efc7cc 100644 --- a/lib/run-all-checks.ts +++ b/lib/run-all-checks.ts @@ -9,7 +9,7 @@ import { checkSourceTracesHavePcbTraces } from "./check-source-traces-have-pcb-t import { checkPcbTracesOutOfBoard } from "./check-trace-out-of-board/checkTraceOutOfBoard" import { checkPcbComponentOverlap } from "./check-pcb-components-overlap/checkPcbComponentOverlap" import { checkPinMustBeConnected } from "./check-pin-must-be-connected" -import { checkI2cMisconfigured } from "./check-i2c-misconfigured" +import { checkI2cSdaConnectedToSclMisconfigured } from "./check-i2c-sda-connected-to-scl" import type { AnyCircuitElement } from "circuit-json" export async function runAllChecks(circuitJson: AnyCircuitElement[]) { @@ -25,6 +25,6 @@ export async function runAllChecks(circuitJson: AnyCircuitElement[]) { ...checkPcbTracesOutOfBoard(circuitJson), ...checkPcbComponentOverlap(circuitJson), ...checkPinMustBeConnected(circuitJson), - ...checkI2cMisconfigured(circuitJson), + ...checkI2cSdaConnectedToSclMisconfigured(circuitJson), ] } diff --git a/tests/lib/check-i2c-misconfigured.test.ts b/tests/lib/check-i2c-sda-connected-to-scl.test.ts similarity index 61% rename from tests/lib/check-i2c-misconfigured.test.ts rename to tests/lib/check-i2c-sda-connected-to-scl.test.ts index d05fc92..7e4d1a4 100644 --- a/tests/lib/check-i2c-misconfigured.test.ts +++ b/tests/lib/check-i2c-sda-connected-to-scl.test.ts @@ -1,20 +1,32 @@ import { expect, test } from "bun:test" -import { checkI2cMisconfigured } from "../../lib/check-i2c-misconfigured" +import { checkI2cSdaConnectedToSclMisconfigured } from "../../lib/check-i2c-sda-connected-to-scl" import type { AnyCircuitElement } from "circuit-json" -test("checkI2cMisconfigured detects SDA connected to SCL", () => { +test("checkI2cSdaConnectedToSclMisconfigured detects SDA connected to SCL", () => { const circuitJson: AnyCircuitElement[] = [ { type: "source_port", source_port_id: "port_sda", name: "SDA", is_configured_for_i2c_sda: true, + source_component_id: "comp_1" + }, + { + type: "source_component", + source_component_id: "comp_1", + name: "U1", }, { type: "source_port", source_port_id: "port_scl", name: "SCL", is_configured_for_i2c_scl: true, + source_component_id: "comp_2" + }, + { + type: "source_component", + source_component_id: "comp_2", + name: "U2", }, { type: "source_trace", @@ -24,14 +36,15 @@ test("checkI2cMisconfigured detects SDA connected to SCL", () => { }, ] as AnyCircuitElement[] - const errors = checkI2cMisconfigured(circuitJson) + const errors = checkI2cSdaConnectedToSclMisconfigured(circuitJson) expect(errors).toHaveLength(1) - expect(errors[0].message).toEqual(expect.stringContaining("I2C")) + expect(errors[0].message).toEqual(expect.stringContaining("Port SCL on U2")) + expect(errors[0].message).toEqual(expect.stringContaining("Port SDA on U1")) expect(errors[0].source_port_ids).toContain("port_sda") expect(errors[0].source_port_ids).toContain("port_scl") }) -test("checkI2cMisconfigured allows SDA connected to SDA", () => { +test("checkI2cSdaConnectedToSclMisconfigured allows SDA connected to SDA", () => { const circuitJson: AnyCircuitElement[] = [ { type: "source_port", @@ -53,6 +66,6 @@ test("checkI2cMisconfigured allows SDA connected to SDA", () => { }, ] as AnyCircuitElement[] - const errors = checkI2cMisconfigured(circuitJson) + const errors = checkI2cSdaConnectedToSclMisconfigured(circuitJson) expect(errors).toHaveLength(0) }) From f82f71d49b9ef742adac3acda8590c0a0b45f1cc Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 10:13:05 +0530 Subject: [PATCH 6/9] Further refine error message for I2C SDA connected to SCL --- lib/check-i2c-sda-connected-to-scl.ts | 11 ++++++++--- tests/lib/check-i2c-sda-connected-to-scl.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/check-i2c-sda-connected-to-scl.ts b/lib/check-i2c-sda-connected-to-scl.ts index 5dd7264..6b1588a 100644 --- a/lib/check-i2c-sda-connected-to-scl.ts +++ b/lib/check-i2c-sda-connected-to-scl.ts @@ -54,21 +54,26 @@ export function checkI2cSdaConnectedToSclMisconfigured( const sortedConflicts = [...conflictingPortIds].sort() const conflictIdStr = sortedConflicts.join("_") - const portDetails = sortedConflicts.map(portId => { + const portDetails = sortedConflicts.map((portId) => { const port = portMap.get(portId) const component = sourceComponents.find( (c) => c.source_component_id === port?.source_component_id, ) const componentName = component?.name ?? "Unknown" const portName = port?.name ?? "Unknown" - return `Port ${portName} on ${componentName}` + const i2cRole = port?.is_configured_for_i2c_sda + ? "I2C SDA" + : port?.is_configured_for_i2c_scl + ? "I2C SCL" + : "Unknown" + return `${componentName}.${portName} (${i2cRole})` }) errors.push({ type: "source_i2c_misconfigured_error", source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, error_type: "source_i2c_misconfigured_error", - message: `I2C SDA and SCL pins are connected together on the same net. To fix this, ensure SDA and SCL are routed to separate nets. Conflicting ports: ${portDetails.join(", ")}`, + message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, source_port_ids: conflictingPortIds, }) } diff --git a/tests/lib/check-i2c-sda-connected-to-scl.test.ts b/tests/lib/check-i2c-sda-connected-to-scl.test.ts index 7e4d1a4..6cc7bca 100644 --- a/tests/lib/check-i2c-sda-connected-to-scl.test.ts +++ b/tests/lib/check-i2c-sda-connected-to-scl.test.ts @@ -38,8 +38,8 @@ test("checkI2cSdaConnectedToSclMisconfigured detects SDA connected to SCL", () = const errors = checkI2cSdaConnectedToSclMisconfigured(circuitJson) expect(errors).toHaveLength(1) - expect(errors[0].message).toEqual(expect.stringContaining("Port SCL on U2")) - expect(errors[0].message).toEqual(expect.stringContaining("Port SDA on U1")) + expect(errors[0].message).toEqual(expect.stringContaining("U2.SCL (I2C SCL)")) + expect(errors[0].message).toEqual(expect.stringContaining("U1.SDA (I2C SDA)")) expect(errors[0].source_port_ids).toContain("port_sda") expect(errors[0].source_port_ids).toContain("port_scl") }) From f78a1c2f5083b7dcb5eed65b105ac895942e1478 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 10:30:21 +0530 Subject: [PATCH 7/9] up --- lib/check-i2c-sda-connected-to-scl.ts | 122 +++++++++--------- .../check-i2c-sda-connected-to-scl.test.ts | 4 +- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/check-i2c-sda-connected-to-scl.ts b/lib/check-i2c-sda-connected-to-scl.ts index 6b1588a..36cbbc2 100644 --- a/lib/check-i2c-sda-connected-to-scl.ts +++ b/lib/check-i2c-sda-connected-to-scl.ts @@ -1,83 +1,83 @@ import type { - AnyCircuitElement, - SourceI2cMisconfiguredError, + AnyCircuitElement, + SourceI2cMisconfiguredError, } from "circuit-json" import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" type SourceComponent = Extract< - AnyCircuitElement, - { source_component_id: string; name: string } + AnyCircuitElement, + { source_component_id: string; name: string } > export function checkI2cSdaConnectedToSclMisconfigured( - circuitJson: AnyCircuitElement[], + circuitJson: AnyCircuitElement[], ): SourceI2cMisconfiguredError[] { - const errors: SourceI2cMisconfiguredError[] = [] + const errors: SourceI2cMisconfiguredError[] = [] - const sourceComponents = circuitJson.filter( - (el): el is SourceComponent => - "source_component_id" in el && - (el.type === "source_component" || el.type.startsWith("source_simple_")), - ) + const sourceComponents = circuitJson.filter( + (el): el is SourceComponent => + "source_component_id" in el && + (el.type === "source_component" || el.type.startsWith("source_simple_")), + ) - // Get all source ports to easily look up their attributes - const sourcePorts = circuitJson.filter( - (el): el is Extract => - el.type === "source_port", - ) - const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) + // Get all source ports to easily look up their attributes + const sourcePorts = circuitJson.filter( + (el): el is Extract => + el.type === "source_port", + ) + const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) - const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) + const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) - for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { - let hasSda = false - let hasScl = false + for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { + let hasSda = false + let hasScl = false - const conflictingPortIds: string[] = [] + const conflictingPortIds: string[] = [] - for (const portId of connectedPortIds) { - const port = portMap.get(portId) - if (!port) continue + for (const portId of connectedPortIds) { + const port = portMap.get(portId) + if (!port) continue - if (port.is_configured_for_i2c_sda) { - hasSda = true - conflictingPortIds.push(portId) - } - if (port.is_configured_for_i2c_scl) { - hasScl = true - conflictingPortIds.push(portId) - } - } + if (port.is_configured_for_i2c_sda) { + hasSda = true + conflictingPortIds.push(portId) + } + if (port.is_configured_for_i2c_scl) { + hasScl = true + conflictingPortIds.push(portId) + } + } - if (hasSda && hasScl) { - // Sort conflicting port IDs to ensure deterministic ID generation - const sortedConflicts = [...conflictingPortIds].sort() - const conflictIdStr = sortedConflicts.join("_") + if (hasSda && hasScl) { + // Sort conflicting port IDs to ensure deterministic ID generation + const sortedConflicts = [...conflictingPortIds].sort() + const conflictIdStr = sortedConflicts.join("_") - const portDetails = sortedConflicts.map((portId) => { - const port = portMap.get(portId) - const component = sourceComponents.find( - (c) => c.source_component_id === port?.source_component_id, - ) - const componentName = component?.name ?? "Unknown" - const portName = port?.name ?? "Unknown" - const i2cRole = port?.is_configured_for_i2c_sda - ? "I2C SDA" - : port?.is_configured_for_i2c_scl - ? "I2C SCL" - : "Unknown" - return `${componentName}.${portName} (${i2cRole})` - }) + const portDetails = sortedConflicts.map((portId) => { + const port = portMap.get(portId) + const component = sourceComponents.find( + (c) => c.source_component_id === port?.source_component_id, + ) + const componentName = component?.name ?? "Unknown" + const portName = port?.name ?? "Unknown" + const i2cRole = port?.is_configured_for_i2c_sda + ? "I2C SDA" + : port?.is_configured_for_i2c_scl + ? "I2C SCL" + : "Unknown" + return `${componentName}.${portName} (${i2cRole})` + }) - errors.push({ - type: "source_i2c_misconfigured_error", - source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, - error_type: "source_i2c_misconfigured_error", - message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, - source_port_ids: conflictingPortIds, - }) - } + errors.push({ + type: "source_i2c_misconfigured_error", + source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, + error_type: "source_i2c_misconfigured_error", + message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, + source_port_ids: conflictingPortIds, + }) } + } - return errors + return errors } diff --git a/tests/lib/check-i2c-sda-connected-to-scl.test.ts b/tests/lib/check-i2c-sda-connected-to-scl.test.ts index 6cc7bca..e158e45 100644 --- a/tests/lib/check-i2c-sda-connected-to-scl.test.ts +++ b/tests/lib/check-i2c-sda-connected-to-scl.test.ts @@ -9,7 +9,7 @@ test("checkI2cSdaConnectedToSclMisconfigured detects SDA connected to SCL", () = source_port_id: "port_sda", name: "SDA", is_configured_for_i2c_sda: true, - source_component_id: "comp_1" + source_component_id: "comp_1", }, { type: "source_component", @@ -21,7 +21,7 @@ test("checkI2cSdaConnectedToSclMisconfigured detects SDA connected to SCL", () = source_port_id: "port_scl", name: "SCL", is_configured_for_i2c_scl: true, - source_component_id: "comp_2" + source_component_id: "comp_2", }, { type: "source_component", From 4df797457700ca7662f9219297a5ebd48a0da3f7 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 10:44:17 +0530 Subject: [PATCH 8/9] Fix duplicate port IDs in conflictingPortIds array --- lib/check-i2c-sda-connected-to-scl.ts | 126 +++++++++++++------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/lib/check-i2c-sda-connected-to-scl.ts b/lib/check-i2c-sda-connected-to-scl.ts index 36cbbc2..bea0a91 100644 --- a/lib/check-i2c-sda-connected-to-scl.ts +++ b/lib/check-i2c-sda-connected-to-scl.ts @@ -1,83 +1,87 @@ import type { - AnyCircuitElement, - SourceI2cMisconfiguredError, + AnyCircuitElement, + SourceI2cMisconfiguredError, } from "circuit-json" import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" type SourceComponent = Extract< - AnyCircuitElement, - { source_component_id: string; name: string } + AnyCircuitElement, + { source_component_id: string; name: string } > export function checkI2cSdaConnectedToSclMisconfigured( - circuitJson: AnyCircuitElement[], + circuitJson: AnyCircuitElement[], ): SourceI2cMisconfiguredError[] { - const errors: SourceI2cMisconfiguredError[] = [] + const errors: SourceI2cMisconfiguredError[] = [] - const sourceComponents = circuitJson.filter( - (el): el is SourceComponent => - "source_component_id" in el && - (el.type === "source_component" || el.type.startsWith("source_simple_")), - ) + const sourceComponents = circuitJson.filter( + (el): el is SourceComponent => + "source_component_id" in el && + (el.type === "source_component" || el.type.startsWith("source_simple_")), + ) - // Get all source ports to easily look up their attributes - const sourcePorts = circuitJson.filter( - (el): el is Extract => - el.type === "source_port", - ) - const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) + // Get all source ports to easily look up their attributes + const sourcePorts = circuitJson.filter( + (el): el is Extract => + el.type === "source_port", + ) + const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) - const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) + const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) - for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { - let hasSda = false - let hasScl = false + for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { + let hasSda = false + let hasScl = false - const conflictingPortIds: string[] = [] + const conflictingPortIds: string[] = [] - for (const portId of connectedPortIds) { - const port = portMap.get(portId) - if (!port) continue + for (const portId of connectedPortIds) { + const port = portMap.get(portId) + if (!port) continue - if (port.is_configured_for_i2c_sda) { - hasSda = true - conflictingPortIds.push(portId) - } - if (port.is_configured_for_i2c_scl) { - hasScl = true - conflictingPortIds.push(portId) - } - } + let shouldAddPort = false + if (port.is_configured_for_i2c_sda) { + hasSda = true + shouldAddPort = true + } + if (port.is_configured_for_i2c_scl) { + hasScl = true + shouldAddPort = true + } + if (shouldAddPort && !conflictingPortIds.includes(portId)) { + conflictingPortIds.push(portId) + } + } - if (hasSda && hasScl) { - // Sort conflicting port IDs to ensure deterministic ID generation - const sortedConflicts = [...conflictingPortIds].sort() - const conflictIdStr = sortedConflicts.join("_") + if (hasSda && hasScl) { + // Sort conflicting port IDs to ensure deterministic ID generation + const sortedConflicts = [...conflictingPortIds].sort() + const conflictIdStr = sortedConflicts.join("_") - const portDetails = sortedConflicts.map((portId) => { - const port = portMap.get(portId) - const component = sourceComponents.find( - (c) => c.source_component_id === port?.source_component_id, - ) - const componentName = component?.name ?? "Unknown" - const portName = port?.name ?? "Unknown" - const i2cRole = port?.is_configured_for_i2c_sda - ? "I2C SDA" - : port?.is_configured_for_i2c_scl - ? "I2C SCL" - : "Unknown" - return `${componentName}.${portName} (${i2cRole})` - }) + const portDetails = sortedConflicts.map((portId) => { + const port = portMap.get(portId) + const component = sourceComponents.find( + (c) => c.source_component_id === port?.source_component_id, + ) + const componentName = component?.name ?? "Unknown" + const portName = port?.name ?? "Unknown" + const i2cRole = port?.is_configured_for_i2c_sda + ? "I2C SDA" + : port?.is_configured_for_i2c_scl + ? "I2C SCL" + : "Unknown" + return `${componentName}.${portName} (${i2cRole})` + }) - errors.push({ - type: "source_i2c_misconfigured_error", - source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, - error_type: "source_i2c_misconfigured_error", - message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, - source_port_ids: conflictingPortIds, - }) + errors.push({ + type: "source_i2c_misconfigured_error", + source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, + error_type: "source_i2c_misconfigured_error", + message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, + source_port_ids: conflictingPortIds, + }) + } } - } - return errors + return errors } From 12700641e2e776d457f66740d387875d3d30df45 Mon Sep 17 00:00:00 2001 From: techmannih Date: Sun, 22 Feb 2026 10:49:19 +0530 Subject: [PATCH 9/9] up --- lib/check-i2c-sda-connected-to-scl.ts | 130 +++++++++++++------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/lib/check-i2c-sda-connected-to-scl.ts b/lib/check-i2c-sda-connected-to-scl.ts index bea0a91..c32e97c 100644 --- a/lib/check-i2c-sda-connected-to-scl.ts +++ b/lib/check-i2c-sda-connected-to-scl.ts @@ -1,87 +1,87 @@ import type { - AnyCircuitElement, - SourceI2cMisconfiguredError, + AnyCircuitElement, + SourceI2cMisconfiguredError, } from "circuit-json" import { getSourcePortConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map" type SourceComponent = Extract< - AnyCircuitElement, - { source_component_id: string; name: string } + AnyCircuitElement, + { source_component_id: string; name: string } > export function checkI2cSdaConnectedToSclMisconfigured( - circuitJson: AnyCircuitElement[], + circuitJson: AnyCircuitElement[], ): SourceI2cMisconfiguredError[] { - const errors: SourceI2cMisconfiguredError[] = [] + const errors: SourceI2cMisconfiguredError[] = [] - const sourceComponents = circuitJson.filter( - (el): el is SourceComponent => - "source_component_id" in el && - (el.type === "source_component" || el.type.startsWith("source_simple_")), - ) + const sourceComponents = circuitJson.filter( + (el): el is SourceComponent => + "source_component_id" in el && + (el.type === "source_component" || el.type.startsWith("source_simple_")), + ) - // Get all source ports to easily look up their attributes - const sourcePorts = circuitJson.filter( - (el): el is Extract => - el.type === "source_port", - ) - const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) + // Get all source ports to easily look up their attributes + const sourcePorts = circuitJson.filter( + (el): el is Extract => + el.type === "source_port", + ) + const portMap = new Map(sourcePorts.map((p) => [p.source_port_id, p])) - const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) + const connMap = getSourcePortConnectivityMapFromCircuitJson(circuitJson) - for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { - let hasSda = false - let hasScl = false + for (const [netId, connectedPortIds] of Object.entries(connMap.netMap)) { + let hasSda = false + let hasScl = false - const conflictingPortIds: string[] = [] + const conflictingPortIds: string[] = [] - for (const portId of connectedPortIds) { - const port = portMap.get(portId) - if (!port) continue + for (const portId of connectedPortIds) { + const port = portMap.get(portId) + if (!port) continue - let shouldAddPort = false - if (port.is_configured_for_i2c_sda) { - hasSda = true - shouldAddPort = true - } - if (port.is_configured_for_i2c_scl) { - hasScl = true - shouldAddPort = true - } - if (shouldAddPort && !conflictingPortIds.includes(portId)) { - conflictingPortIds.push(portId) - } - } + let shouldAddPort = false + if (port.is_configured_for_i2c_sda) { + hasSda = true + shouldAddPort = true + } + if (port.is_configured_for_i2c_scl) { + hasScl = true + shouldAddPort = true + } + if (shouldAddPort && !conflictingPortIds.includes(portId)) { + conflictingPortIds.push(portId) + } + } - if (hasSda && hasScl) { - // Sort conflicting port IDs to ensure deterministic ID generation - const sortedConflicts = [...conflictingPortIds].sort() - const conflictIdStr = sortedConflicts.join("_") + if (hasSda && hasScl) { + // Sort conflicting port IDs to ensure deterministic ID generation + const sortedConflicts = [...conflictingPortIds].sort() + const conflictIdStr = sortedConflicts.join("_") - const portDetails = sortedConflicts.map((portId) => { - const port = portMap.get(portId) - const component = sourceComponents.find( - (c) => c.source_component_id === port?.source_component_id, - ) - const componentName = component?.name ?? "Unknown" - const portName = port?.name ?? "Unknown" - const i2cRole = port?.is_configured_for_i2c_sda - ? "I2C SDA" - : port?.is_configured_for_i2c_scl - ? "I2C SCL" - : "Unknown" - return `${componentName}.${portName} (${i2cRole})` - }) + const portDetails = sortedConflicts.map((portId) => { + const port = portMap.get(portId) + const component = sourceComponents.find( + (c) => c.source_component_id === port?.source_component_id, + ) + const componentName = component?.name ?? "Unknown" + const portName = port?.name ?? "Unknown" + const i2cRole = port?.is_configured_for_i2c_sda + ? "I2C SDA" + : port?.is_configured_for_i2c_scl + ? "I2C SCL" + : "Unknown" + return `${componentName}.${portName} (${i2cRole})` + }) - errors.push({ - type: "source_i2c_misconfigured_error", - source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, - error_type: "source_i2c_misconfigured_error", - message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, - source_port_ids: conflictingPortIds, - }) - } + errors.push({ + type: "source_i2c_misconfigured_error", + source_i2c_misconfigured_error_id: `source_i2c_misconfigured_error_${conflictIdStr}`, + error_type: "source_i2c_misconfigured_error", + message: `${portDetails.join(" is connected to ")} on the same net. To fix this, ensure SDA and SCL are routed to separate nets.`, + source_port_ids: conflictingPortIds, + }) } + } - return errors + return errors }