Skip to content

[RFC / Experiment] Full internal type system: zero any, strict flags, TS test suite (stacked on #4003) - #4004

Open
MrRio wants to merge 7 commits into
typescript-portfrom
typescript-internal-types
Open

[RFC / Experiment] Full internal type system: zero any, strict flags, TS test suite (stacked on #4003)#4004
MrRio wants to merge 7 commits into
typescript-portfrom
typescript-internal-types

Conversation

@MrRio

@MrRio MrRio commented Jul 15, 2026

Copy link
Copy Markdown
Member

Note

Stacked on #4003 (typescript-port). Review only the commits after 543e1f7a. Like its base, this is an experiment/RFC — but it answers the question the base PR deliberately deferred: what does jsPDF look like with a real type system?

What this does

Phase two of the TypeScript port: replaces the mechanical port's any-scaffolding with a full internal type system, converts the test suite to TypeScript, and turns on the strictness the base PR left off.

  • src/types.ts — the internal type system: jsPDFDocument, jsPDFAPI (the plugin surface), jsPDFInternal, Matrix, Font, PageInfo, options/output types, PubSub, patterns. Each plugin module declares the members it adds via declare module "../types.js" interface augmentation in its own file, so doc.internal, plugin methods, and this inside plugins are all real types with autocomplete.
  • Zero any, enforced in lint. npm run lint now runs oxlint with no-explicit-any + no-ts-ignore as errors across src/ and the test suite (@typescript-eslint cannot run against TS7's native compiler — it needs the removed JS API — so the old eslint stack is replaced by oxlint). noImplicitAny and noImplicitThis are ON. The ~2,600-error burn-down is complete: the only escape hatches are ~200 documented as unknown as casts at genuine dynamic boundaries (polyfill installs over DOM types, negative tests feeding invalid inputs, the __private__ test surface) and two vendored files that stay @ts-nocheck (minified libwebp, compiled-CoffeeScript ttffont).
  • Breaking changes (intentional, internal-leaning): internal function-constructors are now ES classes — Matrix, Point, Rectangle, GState, Pattern, ShadingPattern, TilingPattern, PubSub, the whole AcroForm field hierarchy, Cell, BmpDecoder, PDFSecurity, RGBColor, GifReader/GifWriter. Constructing them without new no longer works (jsPDF() itself still supports new-less calls). Class fields are declare-only so babel emits no class-properties transform and instance property layout is byte-compatible.
  • Test system in TypeScript: all 62 spec files are .spec.ts, type-checked by test/tsconfig.json as part of npm run typecheck — every spec is now also a continuous typings test. Browser runs strip types via the karma babelTS preprocessor; the Node jasmine run uses @babel/register with a .js.ts resolution hook (works on Node 20, no native stripping needed). Source-importing lib specs moved to test/specs/libs/ and are excluded from dist-facing deployment/Node runs.
  • Remaining .js reduced further: polyfills.ts converted; the fflate/fast-png shims stay JS deliberately (karma's rollup preprocessor serves them by on-disk path). prettier upgraded to v3 (v1 cannot parse import type), with .prettierrc pinned so the upgrade itself produced no layout churn.

The types found real bugs

The strict pass surfaced (and deliberately preserved, with comments) six more latent bugs on top of the base PR's three — e.g. addSvgAsImage forwards its arguments one slot early into addImage, and the outline plugin passes a string object-id where a number is expected. The specs also exposed ten places where the shipped types/index.d.ts disagrees with the implementation (Font.id is a string, output("bloburi") returns a string, missing Matrix getters…). With this branch, those can't drift again: declarations could now be generated from source and the 1,471-line hand-written file retired.

Verification

Same gates as the base PR, all green: npm run typecheck (src + test projects, noImplicitAny/noImplicitThis) at 0 errors; npm run lint (prettier 3 + no-any) clean; API-parity script reports the surface identical to pre-port master (17 exports / 5 statics / 65 API keys / 170 instance+prototype keys); Node suite 467/0; browser suite 623 at the known environmental baseline; all five deployment suites at baseline; test-typings passes.

What's deliberately NOT here

strictNullChecks stays off — it's the one remaining strictness gap and a large, separately-reviewable burn-down. The hand-written types/index.d.ts still ships unchanged (retiring it in favor of generated declarations is the natural follow-up once this lands).

MrRio added 7 commits July 15, 2026 10:32
…lyfills.ts

- oxlint no-explicit-any/no-ts-ignore wired into npm run lint (replaces the
  eslint stack, which cannot run against TypeScript 7's native compiler)
- Node jasmine run loads .ts specs via @babel/register + a .js->.ts
  require-resolution hook (works on Node 20, no native stripping needed)
- karma serves test/specs/*.spec.ts through the babelTS preprocessor
- typed spec globals in test/globals.d.ts, checked by test/tsconfig.json
- canary: pdfname.spec.mjs -> pdfname.spec.ts runs in browser AND node
- src/polyfills.js -> polyfills.ts (rollup polyfill inputs updated)
…ype support)

prettier@1 cannot parse TypeScript type-only imports. .prettierrc pins
arrowParens/trailingComma to the prettier-1 layout so the upgrade itself
produces no formatting churn.
…nal classes

- src/ typechecks clean under noImplicitAny/noImplicitThis with zero
  explicit any (oxlint-enforced); @ts-nocheck only in the two vendored
  files (WebPDecoder, ttffont)
- internal function-constructors converted to ES classes: Matrix, Point,
  Rectangle, GState, Pattern, ShadingPattern, TilingPattern, PubSub,
  AcroForm hierarchy, Cell, BmpDecoder, PDFSecurity, RGBColor,
  GifReader/GifWriter (new-less internal construction dropped)
- class fields use declare (type-only) so babel emits no class-properties
  transform and instance property layout stays identical
- globalObject typed as globalThis + index signature; polyfill install
  boundaries in Blob/FileSaver typed with documented casts
- runtime gate: API parity identical, node 467/0, browser 623 at baseline
- fixed one conversion regression (context2d getRGBA gradient guard
  excluded function-typed gradient stubs)
- removed dead getter-only writes from acroform.spec (strict-mode modules
  throw where sloppy scripts silently no-oped)
- all 62 spec files typecheck clean in the test project (noImplicitAny on)
- jspdf.unit private surface typed via PrivateSurface + priv() helper
- spec-discovered type gaps fixed in src/types.ts (legacy text() overload,
  getCreationDate overload, optional flags, private members)
- top-level jsPDF global reads made lazy for the Node run
- typescript deployment suite rewired to compare.ts
- CONTRIBUTING: phase-2 conventions (declare fields, no-any policy,
  augmentation pattern, TS test system)
- repo-wide prettier 3 formatting pass
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.

1 participant