app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
master
John Lauer
2.14.3: LED row was throwing on every refresh (shortState, busySince); build now gated on behaviour
92037e6
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
#!/usr/bin/env python3
"""Catch the 2.14.0 bug class: a function that is CALLED but no longer DEFINED.
An over-greedy regex during the toast rewrite deleted the api() helper. Every byte-check
passed, because the markup and CSS were genuinely correct, and every call site swallowed
its own error, so the page rendered an empty panel instead of failing. Only a read-only
eval inside John's live webview found it. This script finds it at build time instead.
It is deliberately conservative: it only reports a name that is called like a function
and is defined NOWHERE in the page and is not a known global. False negatives are fine;
a false positive that blocks a build is not.
"""
import re, sys, pathlib
src = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "src/manage.html").read_text()
scripts = re.findall(r"<script\b[^>]*>(.*?)</script>", src, re.S)
if not scripts:
print("[jscheck] FAIL no <script> blocks found"); sys.exit(1)
js = "\n".join(scripts)
# Strip comments and strings with a single left-to-right scanner. Per-quote regex
# stripping is WRONG: an apostrophe inside a double-quoted string ("don't") makes the
# single-quote pass eat everything up to the next apostrophe, deleting real definitions
# and reporting them as missing. That false-positive storm is exactly how this script
# first failed, so the scanner walks the text once and honours whichever quote opened.
def strip_js(t):
out = []; i = 0; n = len(t)
while i < n:
c = t[i]
if c == "/" and i + 1 < n and t[i+1] == "/":
while i < n and t[i] != "\n": i += 1
elif c == "/" and i + 1 < n and t[i+1] == "*":
i += 2
while i + 1 < n and not (t[i] == "*" and t[i+1] == "/"): i += 1
i += 2
elif c in "'\"`":
q = c; i += 1
while i < n:
if t[i] == "\\": i += 2; continue
if t[i] == q: i += 1; break
# a ${ } inside a template literal holds real code: keep it
if q == "`" and t[i] == "$" and i + 1 < n and t[i+1] == "{":
depth = 1; i += 2; start = i
while i < n and depth:
if t[i] == "{": depth += 1
elif t[i] == "}": depth -= 1
i += 1
out.append(strip_js(t[start:i-1]))
continue
i += 1
out.append('""')
else:
out.append(c); i += 1
return "".join(out)
stripped = strip_js(js)
defined = set()
defined |= set(re.findall(r"function\s+([A-Za-z_$][\w$]*)", stripped))
defined |= set(re.findall(r"(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:function|\()", stripped))
# a declaration list declares EVERY name in it: `let sx,sy,ox,oy,drag=false` declares five,
# and matching only the first is how this script once reported four real declarations as
# implicit globals
for m in re.finditer(r"\b(?:const|let|var)\s+", stripped):
i = m.end(); depth = 0; buf = []
while i < len(stripped):
c = stripped[i]
if c in "([{": depth += 1
elif c in ")]}":
if depth == 0: break
depth -= 1
elif c == ";" and depth == 0: break
elif c == "\n" and depth == 0 and not buf[-1:] == [","]: pass
buf.append(c); i += 1
for part in re.split(r",(?![^([{]*[)\]}])", "".join(buf)):
name = re.match(r"\s*([A-Za-z_$][\w$]*)", part.split("=")[0])
if name: defined.add(name.group(1))
# destructuring: `const {a,b} = x` / `const [a,b] = x`
defined |= set(re.findall(r"[A-Za-z_$][\w$]*", part.split("=")[0])) if part.strip()[:1] in "{[" else set()
defined |= set(re.findall(r"([A-Za-z_$][\w$]*)\s*:\s*(?:async\s*)?function", stripped))
defined |= set(re.findall(r"function\s*\(([^)]*)\)", stripped) and [])
for params in re.findall(r"function[^(]*\(([^)]*)\)", stripped):
defined |= {p.strip().split("=")[0].strip() for p in params.split(",") if p.strip()}
for params in re.findall(r"\(([^()]*)\)\s*=>", stripped):
defined |= {p.strip().split("=")[0].strip() for p in params.split(",") if p.strip()}
defined |= set(re.findall(r"([A-Za-z_$][\w$]*)\s*=>", stripped))
defined |= set(re.findall(r"catch\s*\(\s*([A-Za-z_$][\w$]*)", stripped))
# object-literal method shorthand -- `log(line){...}` inside an object is a DEFINITION,
# and it reads exactly like a call site if you only pattern-match on NAME(
defined |= set(re.findall(r"[{,]\s*(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{", stripped))
GLOBALS = set("""
window document console fetch setTimeout clearTimeout setInterval clearInterval
JSON Math Date Object Array String Number Boolean Promise Error Map Set RegExp
parseInt parseFloat isNaN encodeURIComponent decodeURIComponent alert confirm prompt
AbortController FormData URL URLSearchParams Image Audio Blob File FileReader
requestAnimationFrame cancelAnimationFrame getComputedStyle matchMedia structuredClone
addEventListener removeEventListener dispatchEvent
localStorage sessionStorage navigator location history CustomEvent Event MutationObserver
IntersectionObserver ResizeObserver TextEncoder TextDecoder btoa atob queueMicrotask
Intl Symbol Proxy Reflect BigInt WeakMap WeakSet ArrayBuffer Uint8Array
if for while switch catch return typeof function do else new delete void await yield
async of in instanceof throw case try finally
""".split())
called = set(re.findall(r"(?<![.\w$])([A-Za-z_$][\w$]*)\s*\(", stripped))
missing = sorted(n for n in called if n not in defined and n not in GLOBALS)
# Second class: an IMPLICIT GLOBAL, a bare `name = value` with no declaration anywhere.
# It works only while its assignment happens to run before every read, so it fails exactly
# when something upstream has already gone wrong, turning one fault into two.
assigns = set(re.findall(r"(?:^|[;{}\n])\s*([A-Za-z_$][\w$]*)\s*=(?!=)", stripped))
implicit = sorted(n for n in assigns if n not in defined and n not in GLOBALS)
if implicit:
print("[jscheck] FAIL assigned but never declared (implicit global): " + ", ".join(implicit))
sys.exit(1)
if missing:
print("[jscheck] FAIL called but never defined: " + ", ".join(missing))
print("[jscheck] this is the 2.14.0 bug class (a helper deleted by an edit).")
sys.exit(1)
print(f"[jscheck] OK {len(called)} distinct call targets, all defined")