Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lib/components/base-components/NormalComponent/NormalComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1721,14 +1721,19 @@ export class NormalComponent<
const props = this._parsedProps
const rawProps = this.props as any

const rawPcbX =
typeof rawProps.pcbX === "string" ? rawProps.pcbX : props.pcbX
const rawPcbY =
typeof rawProps.pcbY === "string" ? rawProps.pcbY : props.pcbY

const pcbLeftEdgeX = props.pcbLeftEdgeX ?? rawProps.pcbLeftEdgeX
const pcbRightEdgeX = props.pcbRightEdgeX ?? rawProps.pcbRightEdgeX
const pcbTopEdgeY = props.pcbTopEdgeY ?? rawProps.pcbTopEdgeY
const pcbBottomEdgeY = props.pcbBottomEdgeY ?? rawProps.pcbBottomEdgeY

const hasExplicitPcbPosition =
props.pcbX !== undefined ||
props.pcbY !== undefined ||
rawPcbX !== undefined ||
rawPcbY !== undefined ||
pcbLeftEdgeX !== undefined ||
pcbRightEdgeX !== undefined ||
pcbTopEdgeY !== undefined ||
Expand Down Expand Up @@ -1760,8 +1765,8 @@ export class NormalComponent<

const resolvedPcbX =
this._resolvedPcbCalcOffsetX ??
(props.pcbX !== undefined
? this._resolvePcbCoordinate(props.pcbX, "pcbX")
(rawPcbX !== undefined
? this._resolvePcbCoordinate(rawPcbX, "pcbX")
: pcbLeftEdgeX !== undefined
? this._resolvePcbCoordinate(pcbLeftEdgeX, "pcbX") +
componentWidth / 2
Expand All @@ -1772,8 +1777,8 @@ export class NormalComponent<

const resolvedPcbY =
this._resolvedPcbCalcOffsetY ??
(props.pcbY !== undefined
? this._resolvePcbCoordinate(props.pcbY, "pcbY")
(rawPcbY !== undefined
? this._resolvePcbCoordinate(rawPcbY, "pcbY")
: pcbTopEdgeY !== undefined
? this._resolvePcbCoordinate(pcbTopEdgeY, "pcbY") -
componentHeight / 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,23 @@ export abstract class PrimitiveComponent<
pcb_component_id: string | null = null
cad_component_id: string | null = null
_reportedInvalidPcbCalcWarnings = new Set<string>()

private _reportInvalidComponentPropertyError(
propertyName: string,
message: string,
): void {
if (!this.root || this._reportedInvalidPcbCalcWarnings.has(propertyName)) {
return
}

this.root.db.source_invalid_component_property_error.insert({
source_component_id: this.source_component_id || "",
property_name: propertyName,
message,
error_type: "source_invalid_component_property_error",
})
this._reportedInvalidPcbCalcWarnings.add(propertyName)
}
fallbackUnassignedName?: string

constructor(props: z.input<ZodProps>) {
Expand Down Expand Up @@ -227,7 +244,16 @@ export abstract class PrimitiveComponent<
} = {},
): number {
if (rawValue == null) return 0
if (typeof rawValue === "number") return rawValue
if (typeof rawValue === "number") {
if (Number.isNaN(rawValue)) {
this._reportInvalidComponentPropertyError(
axis,
`Invalid ${axis} value for ${this.componentName}: value is NaN`,
)
return 0
}
return rawValue
}
if (typeof rawValue !== "string") {
throw new Error(
`Invalid ${axis} value for ${this.componentName}: ${String(rawValue)}`,
Expand Down Expand Up @@ -282,30 +308,24 @@ export abstract class PrimitiveComponent<
)

if (includesComponentVariable && !allowComponentVariables) {
if (
this._isInsideFootprint() &&
this.root &&
!this._reportedInvalidPcbCalcWarnings.has(axis)
) {
this.root.db.source_invalid_component_property_error.insert({
source_component_id: this.source_component_id || "",
property_name: axis,
message:
`component-relative calc references are not supported for footprint elements (${this.componentName}); ` +
if (this._isInsideFootprint() && this.root) {
this._reportInvalidComponentPropertyError(
axis,
`component-relative calc references are not supported for footprint elements (${this.componentName}); ` +
`${axis} will be ignored. expression="${rawValue}"`,
error_type: "source_invalid_component_property_error",
})
this._reportedInvalidPcbCalcWarnings.add(axis)
)
}
return 0
}

return evaluateCalcString(rawValue, { knownVariables })
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
throw new Error(
`Invalid ${axis} value for ${this.componentName}: ${message}`,
this._reportInvalidComponentPropertyError(
axis,
`Invalid ${axis} value for ${this.componentName}: ${message}. expression="${rawValue}"`,
)
return 0
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,15 @@ export function Group_doInitialPcbCalcPlacementResolution(
const pcbComponent = db.pcb_component.get(component.pcb_component_id)
if (!pcbComponent) return

const rawPcbX = (component._parsedProps as any).pcbX
const rawPcbY = (component._parsedProps as any).pcbY
const rawComponentProps = component.props as any
const rawPcbX =
typeof rawComponentProps.pcbX === "string"
? rawComponentProps.pcbX
: (component._parsedProps as any).pcbX
const rawPcbY =
typeof rawComponentProps.pcbY === "string"
? rawComponentProps.pcbY
: (component._parsedProps as any).pcbY
const rawPcbLeftEdgeX =
(component._parsedProps as any).pcbLeftEdgeX ??
rawComponentProps.pcbLeftEdgeX
Expand Down Expand Up @@ -242,8 +248,10 @@ function shouldResolvePlacementInCalcPhase(
const parsedProps = component._parsedProps as any
const rawProps = component.props as any

const pcbX = parsedProps.pcbX
const pcbY = parsedProps.pcbY
const pcbX =
typeof rawProps.pcbX === "string" ? rawProps.pcbX : parsedProps.pcbX
const pcbY =
typeof rawProps.pcbY === "string" ? rawProps.pcbY : parsedProps.pcbY
const pcbLeftEdgeX = parsedProps.pcbLeftEdgeX ?? rawProps.pcbLeftEdgeX
const pcbRightEdgeX = parsedProps.pcbRightEdgeX ?? rawProps.pcbRightEdgeX
const pcbTopEdgeY = parsedProps.pcbTopEdgeY ?? rawProps.pcbTopEdgeY
Expand Down Expand Up @@ -286,9 +294,15 @@ function getComponentRefsForCalcPlacement(
component: NormalComponent,
): Set<string> {
const refs = new Set<string>()
const rawPcbX = (component._parsedProps as any).pcbX
const rawPcbY = (component._parsedProps as any).pcbY
const rawComponentProps = component.props as any
const rawPcbX =
typeof rawComponentProps.pcbX === "string"
? rawComponentProps.pcbX
: (component._parsedProps as any).pcbX
const rawPcbY =
typeof rawComponentProps.pcbY === "string"
? rawComponentProps.pcbY
: (component._parsedProps as any).pcbY
const rawPcbLeftEdgeX =
(component._parsedProps as any).pcbLeftEdgeX ??
rawComponentProps.pcbLeftEdgeX
Expand All @@ -303,7 +317,12 @@ function getComponentRefsForCalcPlacement(

const addRefs = (rawValue: unknown) => {
if (typeof rawValue !== "string") return
const identifiers = extractCalcIdentifiers(rawValue)
let identifiers: string[] = []
try {
identifiers = extractCalcIdentifiers(rawValue)
} catch {
return
}
for (const identifier of identifiers) {
if (!identifier.startsWith("board.")) {
refs.add(identifier)
Expand Down
109 changes: 109 additions & 0 deletions tests/components/pcb/calc-pcb-position-malformed-wrapper.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { expect, test } from "bun:test"
import { getTestFixture } from "tests/fixtures/get-test-fixture"

test("pcb calc rejects missing closing paren in calc wrapper for normal component", () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="40mm" height="20mm">
<resistor
name="R1"
footprint="0402"
resistance="1k"
pcbX="calc(board.minX + 1mm"
/>
</board>,
)

circuit.render()

const invalidPropertyErrors =
circuit.db.source_invalid_component_property_error.list()
expect(invalidPropertyErrors.length).toBeGreaterThan(0)

const message = invalidPropertyErrors
.filter((element) => "message" in element)
.map((element) => element.message)
.join("\n")

expect(message).toContain("Invalid pcbX value")

expect(
invalidPropertyErrors.some(
(element) =>
"property_name" in element && element.property_name === "pcbX",
),
).toBe(true)
})

test("pcb calc rejects malformed calc wrapper token for normal component", () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="40mm" height="20mm">
<resistor
name="R1"
footprint="0402"
resistance="1k"
pcbX="calc board.minX + 1mm)"
/>
</board>,
)

circuit.render()

const invalidPropertyErrors =
circuit.db.source_invalid_component_property_error.list()
expect(invalidPropertyErrors.length).toBeGreaterThan(0)

const message = invalidPropertyErrors
.filter((element) => "message" in element)
.map((element) => element.message)
.join("\n")

expect(message).toContain("Invalid pcbX value")

expect(
invalidPropertyErrors.some(
(element) =>
"property_name" in element && element.property_name === "pcbX",
),
).toBe(true)
})

test("pcb calc reports invalid inner calc expression for normal component", () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="40mm" height="20mm">
<resistor
name="R1"
footprint="0402"
resistance="1k"
pcbX="calc(board.minX + )"
/>
</board>,
)

circuit.render()

const invalidPropertyErrors =
circuit.db.source_invalid_component_property_error.list()
expect(invalidPropertyErrors.length).toBeGreaterThan(0)

const message = invalidPropertyErrors
.filter((element) => "message" in element)
.map((element) => element.message)
.join("\n")

expect(message).toContain("Invalid pcbX value")
expect(message).toContain("Unexpected end of expression")
expect(message).toContain('expression="calc(board.minX + )"')

expect(
invalidPropertyErrors.some(
(element) =>
"property_name" in element && element.property_name === "pcbX",
),
).toBe(true)
})
Loading