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
2 changes: 1 addition & 1 deletion lib/convert-easyeda-json-to-tscircuit-soup-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
import { compose, scale, translate, applyToPoint } from "transformation-matrix"
import { mm } from "@tscircuit/mm"
import { mil10ToMm } from "./utils/easyeda-unit-to-mm"
import { normalizePinLabels } from "@tscircuit/core"
import { normalizePinLabels } from "./utils/normalize-pin-labels"
import { DEFAULT_PCB_THICKNESS_MM } from "./constants"
import { normalizeSymbolName } from "./utils/normalize-symbol-name"

Expand Down
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export { convertRawEasyToTsx as convertRawEasyEdaToTs } from "./websafe/convert-
export { normalizeManufacturerPartNumber } from "./utils/normalize-manufacturer-part-number"
export * from "./schemas/easy-eda-json-schema"
export { convertEasyEdaJsonToVariousFormats } from "./convert-easyeda-json-to-various-formats"
export { normalizePinLabels } from "@tscircuit/core"
export { normalizePinLabels } from "./utils/normalize-pin-labels"
83 changes: 83 additions & 0 deletions lib/utils/normalize-pin-labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
export const normalizePinLabels = (pinLabelSets: string[][]): string[][] => {
const normalizedLabels = pinLabelSets.map((labels) =>
labels.filter(
(label) => typeof label === "string" && label.trim().length > 0,
),
)

const requestedPinNumbers = normalizedLabels.map((labels) => {
const numericLabel = labels.find((label) => /^\d+$/.test(label))
return numericLabel ? Number.parseInt(numericLabel, 10) : null
})

const firstOccurrenceByPinNumber = new Map<number, number>()
const reservedPinNumbers = new Set<number>()
for (const [index, requestedPinNumber] of requestedPinNumbers.entries()) {
if (requestedPinNumber == null) continue
reservedPinNumbers.add(requestedPinNumber)
if (!firstOccurrenceByPinNumber.has(requestedPinNumber)) {
firstOccurrenceByPinNumber.set(requestedPinNumber, index)
}
}

const nonNumericTotals = new Map<string, number>()
for (const labels of normalizedLabels) {
for (const label of labels) {
if (/^\d+$/.test(label)) continue
nonNumericTotals.set(label, (nonNumericTotals.get(label) ?? 0) + 1)
}
}

const generatedPinNumbers = new Set<number>()
const duplicateNumericCounts = new Map<number, number>()
const nonNumericSeenCounts = new Map<string, number>()
let nextGeneratedPinNumber = 1

const getNextAvailablePinNumber = () => {
while (
reservedPinNumbers.has(nextGeneratedPinNumber) ||
generatedPinNumbers.has(nextGeneratedPinNumber)
) {
nextGeneratedPinNumber += 1
}

const pinNumber = nextGeneratedPinNumber
generatedPinNumbers.add(pinNumber)
nextGeneratedPinNumber += 1
return pinNumber
}

return normalizedLabels.map((labels, index) => {
const requestedPinNumber = requestedPinNumbers[index]
const primaryPinNumber =
requestedPinNumber != null &&
firstOccurrenceByPinNumber.get(requestedPinNumber) === index
? requestedPinNumber
: getNextAvailablePinNumber()

const outputLabels = [`pin${primaryPinNumber}`]

if (requestedPinNumber != null && primaryPinNumber !== requestedPinNumber) {
const duplicateIndex =
(duplicateNumericCounts.get(requestedPinNumber) ?? 0) + 1
duplicateNumericCounts.set(requestedPinNumber, duplicateIndex)
outputLabels.push(`pin${requestedPinNumber}_alt${duplicateIndex}`)
}

for (const label of labels) {
if (/^\d+$/.test(label)) continue

const totalOccurrences = nonNumericTotals.get(label) ?? 0
if (totalOccurrences <= 1) {
outputLabels.push(label)
continue
}

const occurrenceIndex = (nonNumericSeenCounts.get(label) ?? 0) + 1
nonNumericSeenCounts.set(label, occurrenceIndex)
outputLabels.push(`${label}${occurrenceIndex}`)
}

return outputLabels
})
}
20 changes: 20 additions & 0 deletions tests/websafe/browser-bundle-websafe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, test } from "bun:test"
import { readFile } from "node:fs/promises"

const forbiddenBundlePatterns = [
{ pattern: /_tty:\s*function/, description: "TTY require shim" },
{ pattern: /node_modules\/pretty\/index\.js/, description: "pretty package" },
{ pattern: /graphics-debug/, description: "graphics-debug dependency" },
]

describe("browser bundle websafe guard", () => {
test("browser build does not include known non-websafe runtime baggage", async () => {
await Bun.$`bun run build`.quiet()

const browserBundle = await readFile("dist/browser/index.js", "utf8")

for (const { pattern, description } of forbiddenBundlePatterns) {
expect(browserBundle).not.toMatch(pattern)
}
}, 30_000)
})
Loading