Skip to content

[OP-18993] Convert plugins and entry files to TypeScript (3/3) - #137

Open
myabc wants to merge 9 commits into
code-maintenance/OP-18993-typescript-non-pluginfrom
code-maintenance/OP-18993-typescript-plugins
Open

myabc wants to merge 9 commits into
code-maintenance/OP-18993-typescript-non-pluginfrom
code-maintenance/OP-18993-typescript-plugins

Conversation

@myabc

@myabc myabc commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Ticket

https://community.openproject.org/wp/OP-18993

What are you trying to accomplish?

Third of three PRs that convert this repository to strict TypeScript. It converts all plugins and the entry files, then stops accepting JavaScript. After it, no .js file is left under src or tests. Stacked on the non-plugin PR.

The conversion changes types only. The production bundle built from this branch is byte-identical to the one built from master.

Declaration output for core (the other goal of the work package) is not part of this PR and follows separately.

What approach did you choose and why?

Each area is two commits: a pure rename, then the types. The last two commits type the entry files and remove allowJs.

  • The editor classes declare createCustomized with declare static. The statics are still assigned after the class definitions, in the same order.
  • The window exports core reads (OPConstrainedEditor, OPClassicEditor, OPEditorWatchdog) are declared on Window.
  • src/op-types.ts gains the core services the plugins call: macros, external query configuration, Turbo requests, notifications, timezone and attachments.
  • Our widget toolbar keys and the revisions keys are registered on EditorConfig.
  • The dialog:close document event core fires is declared on DocumentEventMap.

What typing found

Left as they are and marked TODO(OP-18993), because this PR must not change behaviour. Each deserves its own fix:

  • Uploads: OpUploadResourceAdapter#upload() catches a failed request and resolves with undefined, where UploadAdapter requires a response or a rejection. Codex reproduced the consequence: CKEditor then dereferences the missing response and throws, without removing the failed image. This is the most important of the list.
  • Revisions UI: checks record.items.count, which arrays do not have, so the "no revisions" branch only runs when there is no record at all.
  • Custom CSS classes: passes {priority: 'high'} to add(), which takes one argument and ignores it.
  • Work package button and child pages widgets: pass an object where the widget label should be a string.
  • Embedded table widget: reads this.label, which is never assigned.
  • Macro list: reads pluginName from removePlugins entries, which is undefined for entries given as strings.
  • Image attachment lookup: throws when the editor has no resource.
  • Configuration customizer: throws when core passes no openProject configuration.
  • Revisions UI, timestamps: passes a numeric timestamp to TimezoneService#formattedRelativeDateTime(), which core declares as taking a datetime string.
  • Code block downcast: creates a text node from the content attribute, which is missing on a code block upcast from an empty <code>.

Escape hatches in the finished code base

Kind Count
@ts-expect-error 3 (two for private CKEditor API, one for the ignored add() priority above)
as casts 110, of which 18 go through unknown
Non-null assertions (!) 58
any 0

Most casts narrow getAttribute() results, which CKEditor types as unknown, or DOM nodes whose kind a filter already guarantees. Casts that hide a mismatch carry a comment saying why.

Verification

Gate Result
npm run typecheck pass, with allowJs removed
npm test 20 suites, 256 passed, 7 skipped (same as master)
npm run lint pass
npm run build pass
Bundle vs master ckeditor.js and ckeditor.css byte-identical (cmp)
Per-file emit comparison 81 of 81 converted files identical to their .js predecessor
Remaining .js under src and tests 0

Not done: a manual smoke test in core. Given the byte-identical bundle it should show nothing, but it has not been run.

AI involvement

Directed: written by Claude Code from a design agreed with @myabc, then reviewed adversarially by Codex. Codex found no runtime change and flagged the upload defect listed above.

@myabc
myabc added this pull request to stack #138 October 7, 2026 18:40
@myabc
myabc marked this pull request as ready for review October 7, 2026 18:43
Copilot AI balanced review requested due to automatic review settings October 7, 2026 18:43

Copilot AI 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.

🟡 Changes recommended

Several new declarations misstate core contracts and suppress unsafe CKEditor node handling.

1 open finding
What changed in this PR

Converts the remaining plugins, tests, and entry points to strict TypeScript while removing JavaScript support.

Changes:

  • Adds strict typings for plugins and OpenProject integration APIs.
  • Migrates entry points and tests from JavaScript to TypeScript.
  • Updates Webpack, ESLint, and TypeScript configuration.
File Description
webpack.config.js Uses the TypeScript entry point.
tsconfig.json Removes JavaScript support.
tests/​plugins/​op-resizer-guard.test.ts Types resizer guard tests.
tests/​plugins/​code-block.test.ts Types code-block test fixtures.
tests/​helpers/​button-disabler.test.ts Types editor test stubs.
src/​types.d.ts Adds editor globals and dialog events.
src/​plugins/​op-upload-resource-adapter.ts Types the upload adapter.
src/​plugins/​op-upload-plugin.ts Types adapter registration.
src/​plugins/​op-source-code.plugin.ts Types the source preview callback.
src/​plugins/​op-resizer-guard/​op-resizer-guard-plugin.ts Converts the resizer guard plugin.
src/​plugins/​op-macro-wp-quickinfo/​predicate.ts Types the quick-info predicate.
src/​plugins/​op-macro-wp-quickinfo/​op-macro-wp-quickinfo-plugin.ts Types quick-info conversions.
src/​plugins/​op-macro-wp-button/​utils.ts Types work-package widget helpers.
src/​plugins/​op-macro-wp-button/​op-macro-wp-button-toolbar.ts Types toolbar attributes.
src/​plugins/​op-macro-wp-button/​op-macro-wp-button-plugin.ts Converts the plugin entry.
src/​plugins/​op-macro-wp-button/​op-macro-wp-button-editing.ts Types editing conversions.
src/​plugins/​op-macro-wiki-page-link/​op-macro-wiki-page-link-plugin.ts Types wiki-link conversions.
src/​plugins/​op-macro-wiki-page-link/​op-macro-wiki-page-link-create-new.ts Types new-page dialog handling.
src/​plugins/​op-macro-wiki-page-link/​op-macro-wiki-page-link-add-existing.ts Types existing-page dialog handling.
src/​plugins/​op-macro-wiki-page-link/​macro-url.ts Adds the typed URL helper.
src/​plugins/​op-macro-wiki-page-link/​macro-url.js Removes the JavaScript helper.
src/​plugins/​op-macro-wiki-page-link/​insert-wiki-page-link.ts Types wiki-link insertion.
src/​plugins/​op-macro-toc-plugin.ts Types ToC rendering helpers.
src/​plugins/​op-macro-list-plugin.ts Types removed-plugin inspection.
src/​plugins/​op-macro-embedded-table/​utils.ts Types embedded-table helpers.
src/​plugins/​op-macro-embedded-table/​embedded-table-toolbar.ts Types toolbar services.
src/​plugins/​op-macro-embedded-table/​embedded-table-plugin.ts Converts the plugin entry.
src/​plugins/​op-macro-embedded-table/​embedded-table-editing.ts Types embedded-table editing.
src/​plugins/​op-macro-child-pages/​utils.ts Types child-page widget helpers.
src/​plugins/​op-macro-child-pages/​op-macro-child-pages-toolbar.ts Types toolbar attributes.
src/​plugins/​op-macro-child-pages/​op-macro-child-pages-plugin.ts Converts the plugin entry.
src/​plugins/​op-macro-child-pages/​op-macro-child-pages-editing.ts Types child-page editing.
src/​plugins/​op-image-attachment-lookup/​op-image-attachment-lookup-plugin.ts Types attachment converters.
src/​plugins/​op-help-link-plugin/​op-help-link-plugin.ts Converts the help-link plugin.
src/​plugins/​op-custom-css-classes-plugin.ts Types custom-class converters.
src/​plugins/​op-content-revisions/​utils.ts Types revision utilities.
src/​plugins/​op-content-revisions/​ui.ts Types revision dropdown UI.
src/​plugins/​op-content-revisions/​storage.ts Defines revision storage records.
src/​plugins/​op-content-revisions/​op-content-revisions.ts Types the revisions plugin.
src/​plugins/​op-content-revisions/​command.ts Types revision command arguments.
src/​plugins/​op-attachment-listener-plugin.ts Types attachment-removal handling.
src/​plugins/​code-block/​widget.ts Types code-block widget helpers.
src/​plugins/​code-block/​converters.ts Types code-block converters.
src/​plugins/​code-block/​code-block.ts Converts the plugin entry.
src/​plugins/​code-block/​code-block-toolbar.ts Types toolbar attributes.
src/​plugins/​code-block/​code-block-editing.ts Types editing integration.
src/​plugins/​code-block/​click-observer.ts Types double-click observation.
src/​op-types.ts Defines OpenProject integration contracts.
src/​op-plugins.ts Converts the plugin registry.
src/​op-config-customizer.ts Types configuration customization.
src/​op-ckeditor.ts Converts editor entry classes.
src/​op-ckeditor-config.ts Types the default editor configuration.
src/​helpers/​button-disabler.ts Types toolbar disabling helpers.
README.md Documents strict TypeScript checking.
eslint.config.mjs Removes obsolete JavaScript lint rules.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/op-types.ts
Comment on lines +79 to +81
timezone: {
formattedRelativeDateTime(timestamp: number): string;
};
myabc added 9 commits October 7, 2026 21:14
Pure rename, including its test; types follow in the next commit.

https://community.openproject.org/wp/OP-18993
Adds the macros service to the OpenProject service types, with the
code block editor it opens. Runtime code is unchanged.

https://community.openproject.org/wp/OP-18993
Types the work package button, embedded table, child pages, wiki page
link, quickinfo, table of contents and macro list plugins, and adds the
core services they call to the service types. Declares the dialog:close
document event that core fires. Runtime code is unchanged.

Typing surfaced four things, left as they are and marked TODO:

- The work package button and child pages widgets pass an object where
  the widget label should be a string.
- The embedded table widget reads a label that is never assigned.
- The macro list reads pluginName from removePlugins entries, which is
  undefined for entries given as strings.

https://community.openproject.org/wp/OP-18993
Pure rename, including the resizer guard test; types follow in the
next commit.

https://community.openproject.org/wp/OP-18993
Types content revisions, custom CSS classes, image attachment lookup,
uploads, the attachment listener, the source toggle and the resizer
guard test, and adds the core services they call to the service types.
Runtime code is unchanged.

The button disabler now takes CKEditor's Editor: its UI view declares
the toolbar, so only the private item list needs a cast.

Typing surfaced these, left as they are and marked TODO:

- Revisions UI checks `items.count`, which arrays do not have.
- Custom CSS classes passes a priority to add(), which ignores it.
- The upload adapter resolves with undefined when a request fails.
- Image attachment lookup throws when the editor has no resource.

https://community.openproject.org/wp/OP-18993
Pure rename; types and the webpack entry follow in the next commit.

https://community.openproject.org/wp/OP-18993
Types the plugin list, default configuration, configuration customizer
and editor classes, and points webpack at the renamed entry.

The editor classes declare createCustomized with `declare static`, so
the statics are still assigned after the class definitions, in the
same order as before. The window exports that core reads are declared
on Window. Our widget toolbar keys are registered on EditorConfig.

https://community.openproject.org/wp/OP-18993
No .js file is left under src or tests, so allowJs goes and the lint
blocks for JavaScript sources and tests go with it. Jest keeps
transforming .js because dependencies are transformed too.

https://community.openproject.org/wp/OP-18993
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants