Skip to content

Commit 0d1d8b8

Browse files
authored
refactor(superdoc): tighten @param {Object} on three callback methods (SD-2867 phase B) (#3061)
* refactor(superdoc): type createSuperdocVueApp return shape (SD-2867 phase B) `createSuperdocVueApp()` returned `Object` per its JSDoc, so the five fields SuperDoc.js destructures from the call (`app`, `pinia`, `superdocStore`, `commentsStore`, `highContrastModeStore`) all resolved to `Object` for any consumer enabling `// @ts-check`. Five TS2339 'Property does not exist on type Object' errors at SuperDoc.js line 464 — and inside the SD-2867 ratchet that turns each into a gate failure. Promotes the return type to a named `SuperdocVueAppRefs` typedef that imports `vue.App`, `pinia.Pinia`, and the store types via `ReturnType<typeof useSuperdocStore>` etc. The shape is internal-only (this typedef is not on the public Modules / Config surface), so adding it here doesn't widen the customer-visible surface. Verified: pnpm --filter superdoc run check:jsdoc passes (3 gated files clean); the consumer-typecheck matrix passes 31/31; the postbuild declaration audit reports no FAIL findings; pnpm --filter superdoc test passes 944/944. This is groundwork for SD-2867 phase B: closing each cluster of SuperDoc.js TS errors so the file can eventually enroll in `CHECKED_FILES`. Five errors closed; ~133 remain across implicit-any params, strict-null guards, and other type-not-assignable callsites. Each cluster will land as its own focused commit. * refactor(superdoc): add RuntimeDocument typedef for runtime doc shape (SD-2867 phase B) The internal `doc` parameter in SuperDoc.js's private methods (#applyDocumentMode, #attachExternalCollaboration, etc.) is shaped differently from the public `Document` interface: SuperDoc attaches a runtime `role`, `getEditor()`, and `getPresentationEditor()` to each entry during init. Today those methods are typed `@param {Object} doc`, so any attempt to enable `// @ts-check` on SuperDoc.js fails with TS2339 errors at every `doc.getEditor()` / `doc.getPresentationEditor()` / `doc.role` access (~10 sites). Adds a `RuntimeDocument` typedef in core/types/index.ts that extends the public `Document` with the runtime-attached fields. Updates #applyDocumentMode's `@param {Object} doc` to `@param {RuntimeDocument} doc` as the first call site to migrate. The typedef is internal-only and not on the public superdoc surface (not in the public typedef block of packages/superdoc/src/index.js), so consumers cannot import or pass these fields from outside. This is groundwork: subsequent SD-2867 phase B PRs will migrate the remaining `@param {Object} doc` sites and then enable @ts-check on the methods involved. Verified: pnpm --filter superdoc run check:jsdoc passes (3 gated files clean); consumer matrix passes 31/31; declaration audit reports no FAIL findings; published .d.ts surface unchanged (RuntimeDocument is not re-exported from the public superdoc entry). * refactor(superdoc): type #initCollaboration param as Modules (SD-2867 phase B) #initCollaboration's @param was typed `Object`, so any consumer that enables `// @ts-check` on SuperDoc.js fails at every destructure of the function's parameter (`{ collaboration, comments }`) and at every read of `collaborationModuleConfig.{ydoc,provider}` and `commentsConfig.*`. Two TS2339s on line 519, plus all the downstream property accesses up to line 605. The actual call site at line 295 passes `this.config.modules`, so the right shape is `Modules` (already publicly exported). Tightens the @param to `Modules` and the @returns to `Promise<Document[] | undefined>` (which is what the function actually returns; the value is informational, not consumed by the caller). Verified locally: with `// @ts-check` enabled on SuperDoc.js, `#initCollaboration`'s body (lines 519-605) is fully type-clean. The remaining error at line 606 is a downstream callsite (passing `User | undefined` to a function expecting `Object`) and belongs to the next phase B cluster, not this one. This is groundwork; SuperDoc.js does not enroll under the gate yet. Verified: pnpm --filter superdoc run check:jsdoc passes (3 gated files clean); consumer matrix passes 31/31; declaration audit reports no FAIL findings; published .d.ts surface unchanged. * refactor(superdoc): tighten @param {Object} on three callback methods (SD-2867 phase B) Three private/public callback methods on SuperDoc had `@param {Object}` JSDoc that masked their actual contract: - addSharedUser(user) — typed as Object; actually expects User (the function reads u.email, which is a User field). - onContentError({ error, editor }) — typed as Object; actually expects an Error/Editor pair forwarded to config.onContentError. - canPerformPermission({ permission, role, isInternal, comment, trackedChange }) — typed as Object; the params are read at the body and forwarded into the permissions resolver chain. Promoted to a structural typedef with permission/role/isInternal/comment/ trackedChange optional (the function defaults `= {}` and treats missing permission as a no-op via the early `if (!permission) return false` guard). Skipped goToSearchResult: the upstream `commands.goToSearchResult` expects a private `SearchMatch` shape (id/from/to/text) that is not on the public surface yet. Tightening that requires either exporting SearchMatch publicly or asserting at the call site; deferring to a separate follow-up. Left a comment on the JSDoc documenting the deferral. This is groundwork for SD-2867 phase B's gate enrollment of SuperDoc.js. Each subsequent PR closes one cluster. Verified: pnpm --filter superdoc run check:jsdoc passes (3 gated files clean); consumer matrix passes 31/31; declaration audit reports no FAIL findings; published .d.ts surface unchanged.
1 parent 81337ac commit 0d1d8b8

3 files changed

Lines changed: 85 additions & 11 deletions

File tree

packages/superdoc/src/core/SuperDoc.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ export class SuperDoc extends EventEmitter {
960960

961961
/**
962962
* Add a user to the shared users list
963-
* @param {Object} user The user to add
963+
* @param {User} user The user to add
964964
* @returns {void}
965965
*/
966966
addSharedUser(user) {
@@ -979,9 +979,7 @@ export class SuperDoc extends EventEmitter {
979979

980980
/**
981981
* Triggered when there is an error in the content
982-
* @param {Object} param0
983-
* @param {Error} param0.error The error that occurred
984-
* @param {Editor} param0.editor The editor that caused the error
982+
* @param {{ error: Error, editor: Editor }} params
985983
*/
986984
onContentError({ error, editor }) {
987985
const { documentId } = editor.options;
@@ -1078,12 +1076,19 @@ export class SuperDoc extends EventEmitter {
10781076
* Used by downstream consumers (toolbar, context menu, commands) to keep
10791077
* tracked-change affordances consistent with customer overrides.
10801078
*
1081-
* @param {Object} params
1082-
* @param {string} params.permission Permission key to evaluate
1083-
* @param {string} [params.role=this.config.role] Role to evaluate against
1084-
* @param {boolean} [params.isInternal=this.config.isInternal] Internal/external flag
1085-
* @param {Object|null} [params.comment] Comment object (if already resolved)
1086-
* @param {Object|null} [params.trackedChange] Tracked change metadata (id, attrs, etc.)
1079+
* `comment` and `trackedChange` carry an open index signature because
1080+
* the function forwards the full payload to `isAllowed()`; tracked-change
1081+
* payloads from the editor include `type`, `attrs`, `from`, `to`,
1082+
* `segments`, and the comment objects passed by consumers vary in shape.
1083+
* The named fields below are the ones this method reads directly.
1084+
*
1085+
* @param {{
1086+
* permission?: string,
1087+
* role?: string,
1088+
* isInternal?: boolean,
1089+
* comment?: (object & Record<string, unknown>) | null,
1090+
* trackedChange?: ({ id?: string, commentId?: string, comment?: unknown } & Record<string, unknown>) | null,
1091+
* }} [params]
10871092
* @returns {boolean}
10881093
*/
10891094
canPerformPermission({
@@ -1436,7 +1441,7 @@ export class SuperDoc extends EventEmitter {
14361441

14371442
/**
14381443
* Go to the next search result
1439-
* @param {Object} match The match object
1444+
* @param {Object} match The match object (returned as-is by `superdoc.search()`; pass it through unchanged). Stays loose here because the upstream `commands.goToSearchResult` expects a private `SearchMatch` shape that is not yet on the public surface; tightening this is a separate follow-up.
14401445
* @returns {void}
14411446
*/
14421447
goToSearchResult(match) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Consumer typecheck: SuperDoc.canPerformPermission must accept the wide
3+
* payloads consumers produce (SD-2867 phase B).
4+
*
5+
* The method forwards `comment` and `trackedChange` to `isAllowed()`
6+
* unchanged, and the editor produces tracked-change objects with `type`,
7+
* `attrs`, `from`, `to`, `segments`, etc. A typedef that closes the shape
8+
* to only `{ id, commentId, comment }` rejects valid payloads under
9+
* strict TS even though the runtime accepts them.
10+
*
11+
* This fixture pins the contract: each call below must compile under
12+
* strict mode. If a future change re-narrows `comment` or `trackedChange`,
13+
* the line stops compiling and CI fails.
14+
*/
15+
import { SuperDoc } from 'superdoc';
16+
17+
declare const sd: SuperDoc;
18+
19+
// Wide trackedChange payload like the editor's permission helper produces.
20+
sd.canPerformPermission({
21+
permission: 'edit',
22+
trackedChange: {
23+
id: 'tc-1',
24+
type: 'insert',
25+
attrs: { color: 'red' },
26+
from: 0,
27+
to: 5,
28+
segments: [],
29+
comment: { id: 'c-1', body: 'note' },
30+
},
31+
});
32+
33+
// Wide comment payload (consumer-defined comment shapes).
34+
sd.canPerformPermission({
35+
permission: 'edit',
36+
comment: { id: 'c-1', body: 'note', author: { name: 'A' } },
37+
});
38+
39+
// No-args / empty payload — function defaults `= {}` and bails on missing
40+
// permission via `if (!permission) return false`.
41+
sd.canPerformPermission();
42+
sd.canPerformPermission({});
43+
44+
// Common minimal payload.
45+
sd.canPerformPermission({ permission: 'comment' });

tests/consumer-typecheck/typecheck-matrix.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,30 @@ const scenarios = [
425425
files: ['src/internal-fields-stripped.ts'],
426426
mustPass: true,
427427
},
428+
// SD-2867 phase B: SuperDoc.canPerformPermission forwards `comment` and
429+
// `trackedChange` to isAllowed() unchanged, so the public contract must
430+
// accept the wide payloads the editor's permission helper produces
431+
// (tracked-change `type`, `attrs`, `from`, `to`, `segments`, etc.). The
432+
// fixture pins this so a future PR cannot re-narrow the typedef into a
433+
// closed shape that rejects valid runtime payloads.
434+
{
435+
name: 'bundler / canPerformPermission wide payloads (SD-2867)',
436+
module: 'ESNext',
437+
moduleResolution: 'bundler',
438+
skipLibCheck: true,
439+
strict: true,
440+
files: ['src/can-perform-permission-payload.ts'],
441+
mustPass: true,
442+
},
443+
{
444+
name: 'node16 / canPerformPermission wide payloads (SD-2867)',
445+
module: 'Node16',
446+
moduleResolution: 'node16',
447+
skipLibCheck: true,
448+
strict: true,
449+
files: ['src/can-perform-permission-payload.ts'],
450+
mustPass: true,
451+
},
428452
];
429453

430454
const tscPath = join(__dirname, 'node_modules', '.bin', 'tsc');

0 commit comments

Comments
 (0)