Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
34 changes: 28 additions & 6 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,34 @@ export type Footprinter = {
}

const normalizeDefinition = (def: string): string => {
return def
.trim()
.replace(/^sot23-(\d+)(?=_|$)/i, "sot23_$1")
.replace(/^sot-223-(\d+)(?=_|$)/i, "sot223_$1")
.replace(/^to-220f-(\d+)(?=_|$)/i, "to220f_$1")
.replace(/^jst_(ph|sh|zh)_(\d+)(?=_|$)/i, "jst$2_$1")
let normalized = def.trim()

// Normalize JST variant format: jst_ph_4 -> jst4_ph
normalized = normalized.replace(/^jst_(ph|sh|zh)_(\d+)(?=_|$)/i, "jst$2_$1")

// Handle sot23-N specifically (digits in prefix, not caught by general rule)
normalized = normalized.replace(/^sot23-(\d+)(?=_|$)/i, "sot23_$1")

// Normalize hyphenated package names commonly found in datasheets.
// The pattern targets names where letters precede a hyphen followed by
// digits (with optional trailing letters), e.g.:
// sot-23 -> sot23 sot-23-5 -> sot23_5
// to-92 -> to92 to-220f-3 -> to220f_3
// sod-123 -> sod123 sod-323f -> sod323f
// soic-8 -> soic8 qfn-32 -> qfn32
// tssop-16 -> tssop16 lqfp-48 -> lqfp48
// bga-100 -> bga100 ms-012 -> ms012
//
// Names like VSON8-1EP (digit before hyphen) are intentionally NOT matched.
normalized = normalized.replace(
/^([a-zA-Z]+)-(\d+[a-zA-Z]*)(?:-(\d+))?(?=_|$)/i,
(_match, prefix, body, pinCount) => {
const base = `${prefix.toLowerCase()}${body.toLowerCase()}`
return pinCount ? `${base}_${pinCount}` : base
},
)

return normalized
}

export const string = (def: string): Footprinter => {
Expand Down
37 changes: 37 additions & 0 deletions tests/hyphenated-names.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

/**
* Hyphenated package names are the standard format found in datasheets and
* part catalogs (e.g. SOT-23, TO-220, SOD-123). The string parser should
* treat them identically to the concatenated form (sot23, to220, sod123).
*
* This test covers representative examples from each package family.
*/

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("hyphenated SOT family resolves like concatenated form", () => {
// SOT-23 (basic), SOT-223-5 (with pin count), SOT-89 (two digits)
expect(getPadCount(fp.string("sot-23").circuitJson())).toBe(
getPadCount(fp.string("sot23").circuitJson()),
)
expect(getPadCount(fp.string("sot-223-5").circuitJson())).toBe(
getPadCount(fp.string("sot223_5").circuitJson()),
)
expect(getPadCount(fp.string("sot-89").circuitJson())).toBe(
getPadCount(fp.string("sot89").circuitJson()),
)
expect(getPadCount(fp.string("sot-323").circuitJson())).toBe(
getPadCount(fp.string("sot323").circuitJson()),
)
expect(getPadCount(fp.string("sot-563").circuitJson())).toBe(
getPadCount(fp.string("sot563").circuitJson()),
)
expect(getPadCount(fp.string("sot-963").circuitJson())).toBe(
getPadCount(fp.string("sot963").circuitJson()),
)
})
19 changes: 19 additions & 0 deletions tests/hyphenated-names2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("hyphenated TO family resolves like concatenated form", () => {
expect(getPadCount(fp.string("to-92").circuitJson())).toBe(
getPadCount(fp.string("to92").circuitJson()),
)
expect(getPadCount(fp.string("to-220").circuitJson())).toBe(
getPadCount(fp.string("to220").circuitJson()),
)
expect(getPadCount(fp.string("to-220f-3").circuitJson())).toBe(
getPadCount(fp.string("to220f_3").circuitJson()),
)
})
28 changes: 28 additions & 0 deletions tests/hyphenated-names3.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("hyphenated SOD family resolves like concatenated form", () => {
expect(getPadCount(fp.string("sod-123").circuitJson())).toBe(
getPadCount(fp.string("sod123").circuitJson()),
)
expect(getPadCount(fp.string("sod-323").circuitJson())).toBe(
getPadCount(fp.string("sod323").circuitJson()),
)
expect(getPadCount(fp.string("sod-523").circuitJson())).toBe(
getPadCount(fp.string("sod523").circuitJson()),
)
expect(getPadCount(fp.string("sod-80").circuitJson())).toBe(
getPadCount(fp.string("sod80").circuitJson()),
)
expect(getPadCount(fp.string("sod-882").circuitJson())).toBe(
getPadCount(fp.string("sod882").circuitJson()),
)
expect(getPadCount(fp.string("sod-123f").circuitJson())).toBe(
getPadCount(fp.string("sod123f").circuitJson()),
)
})
28 changes: 28 additions & 0 deletions tests/hyphenated-names4.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("hyphenated IC packages resolve like concatenated form", () => {
expect(getPadCount(fp.string("soic-8").circuitJson())).toBe(
getPadCount(fp.string("soic8").circuitJson()),
)
expect(getPadCount(fp.string("dip-8").circuitJson())).toBe(
getPadCount(fp.string("dip8").circuitJson()),
)
expect(getPadCount(fp.string("qfn-32").circuitJson())).toBe(
getPadCount(fp.string("qfn32").circuitJson()),
)
expect(getPadCount(fp.string("tssop-16").circuitJson())).toBe(
getPadCount(fp.string("tssop16").circuitJson()),
)
expect(getPadCount(fp.string("lqfp-48").circuitJson())).toBe(
getPadCount(fp.string("lqfp48").circuitJson()),
)
expect(getPadCount(fp.string("ms-012").circuitJson())).toBe(
getPadCount(fp.string("ms012").circuitJson()),
)
})
22 changes: 22 additions & 0 deletions tests/hyphenated-names5.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("uppercase hyphenated names resolve correctly", () => {
expect(getPadCount(fp.string("SOT-23").circuitJson())).toBe(
getPadCount(fp.string("sot23").circuitJson()),
)
expect(getPadCount(fp.string("TO-92").circuitJson())).toBe(
getPadCount(fp.string("to92").circuitJson()),
)
expect(getPadCount(fp.string("SOD-123").circuitJson())).toBe(
getPadCount(fp.string("sod123").circuitJson()),
)
expect(getPadCount(fp.string("SOIC-8").circuitJson())).toBe(
getPadCount(fp.string("soic8").circuitJson()),
)
})
16 changes: 16 additions & 0 deletions tests/hyphenated-names6.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test, expect } from "bun:test"
import { fp } from "../src/footprinter"

const getPadCount = (circuitJson: any[]): number =>
circuitJson.filter(
(e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole",
).length

test("hyphenated names with extra parameters resolve correctly", () => {
expect(getPadCount(fp.string("soic-8_w5.3mm").circuitJson())).toBe(
getPadCount(fp.string("soic8_w5.3mm").circuitJson()),
)
expect(getPadCount(fp.string("dip-8_w7.62mm").circuitJson())).toBe(
getPadCount(fp.string("dip8_w7.62mm").circuitJson()),
)
})
Loading