Skip to content

fix(package): parse static imports with TypeScript - #652

Merged
drewstone merged 1 commit into
mainfrom
fix/syntax-aware-static-import-scanner
Jul 28, 2026
Merged

fix(package): parse static imports with TypeScript#652
drewstone merged 1 commit into
mainfrom
fix/syntax-aware-static-import-scanner

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Summary

  • parse shipped JavaScript with the existing TypeScript compiler AST instead of matching raw text
  • detect ESM imports and re-exports plus CommonJS require calls while permitting dynamic imports
  • run 20 focused synthetic cases before the packed-package verification

Checks

  • pnpm run verify:static-imports
  • pnpm run verify:package
  • pnpm test

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 2d624e95

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-28T20:33:43Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 75.6s (2 bridge agents)
Total 75.6s

💰 Value — sound

Replaces a fragile regex static-import scanner with a TypeScript-AST parser using an already-installed dependency, fixing real false positives/negatives and fronting a fast synthetic test before the expensive packed-package build.

  • What it does: Adds scripts/lib/static-imports.mjs exporting findStaticPackageImports(source, packageNames), which parses JS with ts.createSourceFile and walks the AST to collect ESM import/export specifiers and CommonJS require/module.require string-literal arguments, intentionally skipping dynamic import(). verify-package-exports.mjs drops its hand-rolled hasStaticImport regex and `assertEdgeUn
  • Goals it achieves: Make the edge-unsafe-import release gate (Cloudflare rejects Workers that statically import graceful-fs/proper-lockfile) actually correct: the old regex mis-classified commented-out code, string/template literals containing the word import, member-require calls, and inline-commented specifiers. The new gate catches real static imports the regex missed (require(\x`), module.require('x'
  • Assessment: Sound and in-grain. typescript is already a devDependency and already used identically in scripts/gen-primitive-catalog.mjs:51, so no new dependency. The chosen primitive (ts.createSourceFile, syntactic-only — no createProgram/type-checker) is the lightest correct tool. Placement in scripts/lib/ matches the existing packed-package-test.mjs reuse pattern. Returning the matching package
  • Better / existing approach: Searched scripts/ for existing equivalents (acorn, meriyah, espree, @babel/parse, any prior static-import scanner) — none found; only gen-primitive-catalog.mjs uses the TS compiler, and it does full-program type-checker extraction for a different purpose (symbol/JSDoc cataloging), so it is not reusable here. The new file is the right factoring. none — this is the right approach.
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content

🎯 Usefulness — sound

Replaces a fragile regex static-import matcher with a TypeScript AST parse, fixing shipped false-positives on commented/string-embedded import text; fully wired into CI and publish.

  • Integration: Fully reachable. findStaticPackageImports is consumed at scripts/verify-package-exports.mjs:651 inside assertNoEdgeUnsafeStaticImports (the Cloudflare-builtin-patch safety gate). The new standalone runner scripts/verify-static-imports.mjs is exposed as pnpm run verify:static-imports (package.json:120) and prepended into verify:package (package.json:124), which runs in .github/workflows/ci.yml:
  • Fit with existing patterns: Matches the codebase grain. import ts from 'typescript' is already used the same way at scripts/gen-primitive-catalog.mjs:51 and tests/testing-fixture.test.ts:15; typescript is a catalog dependency (pnpm-workspace.yaml:28, package.json:146). The new code follows the existing scripts/lib/*.mjs factoring (cf. scripts/lib/packed-package-test.mjs). It replaces an inline regex (hasStaticImport) plus
  • Real-world viability: Holds up beyond the happy path. AST parsing is comment- and context-immune: the visitor catches ImportDeclaration/ExportDeclaration module specifiers, CommonJS require() and module.require(), package + subpath specifiers, and no-substitution template literals (isStringLiteralLike); it correctly skips dynamic import() (callee is import, not require), non-string args, lookalike packages (proper-
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260728T203917Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — 2d624e95

Review health 100/100 · Reviewer score 83/100 · Confidence 80/100 · 4 findings (4 low)

glm: Correctness 83 · Security 83 · Testing 83 · Architecture 83

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 4/4 planned shots over 4 changed files. Global verifier still owns final merge decision.

🟡 LOW require() inside function bodies is conservatively flagged as static — scripts/lib/static-imports.mjs

The AST visitor walks all nodes including those inside function bodies, so require('graceful-fs') inside a never-called function is flagged as a module-load-time import. This is NOT a regression — the old regex had the same behavior — and it is conservative (false-positive blocks are safe; false-negatives would risk Cloudflare Worker rejection code 10021). For a future iteration, scope-aware filtering (only top-level or class-field-initializer requires) would reduce false positives, but the current behavior is correct for the security gate's purpose since bundlers statically resolve require() regardless of call site.

🟡 LOW Loop variable named specifier is actually a package name — scripts/verify-package-exports.mjs

findStaticPackageImports returns a filtered subset of the packageNames argument (i.e., 'graceful-fs' or 'proper-lockfile'), not the full import specifier. The loop variable specifier is therefore technically a package name. This is purely cosmetic and behaviorally identical to the old code (which also pushed the edgeUnsafeStaticImports array member), but renaming to packageName would clarify intent.

🟡 LOW No coverage of templated require with substitution — scripts/verify-static-imports.mjs

The suite covers require(\proper-lockfile`)(no substitution, IS StringLiteralLike → true) but does not assert the negativerequire(`${variable}`)` (TemplateExpression, NOT StringLiteralLike → should be false). The implementation handles it correctly today (isStringLiteralLike returns false for TemplateHead/TemplateMiddle/TemplateTail), but there is no regression guard. Add one negative case to lock the behavior; non-blocking since production code is correct.

🟡 LOW Test does not exercise sourceFileName / non-JS script kinds — scripts/verify-static-imports.mjs

All cases call findStaticPackageImports with the default sourceFileName='shipped.js', so ts.createSourceFile infers ScriptKind.JS. Production only scans .js/.mjs/.cjs (ownJavascriptFiles), so this is currently fine, but if the scan ever extends to .ts/.tsx the test would not catch a script-kind regression. Non-blocking; document or add a case if scope expands.


tangletools · 2026-07-28T20:42:51Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — 4 non-blocking findings — 2d624e95

Full multi-shot audit completed 4/4 planned shots over 4 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-28T20:42:51Z · immutable trace

@drewstone
drewstone merged commit 15f1ee3 into main Jul 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants