Replace unsafe/legacy types in ui-kit/components - #729
Merged
fpigeonjr merged 2 commits intoSep 11, 2026
Conversation
Replaces unsafe and legacy TypeScript types (@typescript-eslint/no-explicit-any, no-unsafe-function-type) across src/ui-kit/components, as a slice of #586, following the approach used in #702/#713. - modal.component.ts: typed EventEmitter outputs (unknown[]), types map, scroll helpers, and openModal args. - comments.component.ts/spec.ts: typed form/observable streams against Comment/FormGroup, replaced ad-hoc keyIdentified handling. - sidenav.service.ts, sidenav.component.ts, menu-item.component.ts: widened any/any[] to the existing MenuItem interface (sidenav.service.ts is a frozen consumer-deep-import — no signature changes, only precise typing); added MenuItem#selection. - image.component.ts: typed Observable/EventEmitter streams and drag/drop handlers against DOM event types. - actions/action-interface.ts: callback?: Function -> () => unknown; typed action-button/actions-dropdown EventEmitters against SamActionInterface. - alert.component.ts, alert-footer.service.ts, multiselect-dropdown.component.ts, breadcrumbs.component.ts, accordion.component.ts, header.component.ts: typed EventEmitter outputs and internal fields against existing types (AlertType, OptionsType, IBreadcrumb) instead of any. - Spec files across the directory: replaced any-typed fixtures/locals with ComponentFixture<T> and concrete payload types. Public API and consumer-compiled source compatibility preserved throughout — no breaking signature changes. Verification: - npx eslint src/ui-kit/components: 0 no-explicit-any/no-unsafe-function-type findings remain (108 resolved); 10 unrelated Angular-style warnings (no-output-native/no-output-on-prefix/no-empty-lifecycle-method/ no-input-rename) left untouched, out of scope for this issue. - npx tsc -p tsconfig.json --noEmit: no new errors vs. pre-change baseline. - npm --prefix test-app test: 155 files / 1423 tests pass; coverage gate passes (89.84%/79.68%/86.98%/89.58%, all above floor). - npm run format:check: clean. - eslint-baseline.json root lowered 924 -> 457 via lint:baseline:bump. Closes #704
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved API compatibility, event-contract, and strict-template compilation issues remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Replaces unsafe TypeScript types across src/ui-kit/components and lowers the ESLint baseline from 924 to 457 warnings.
Changes:
- Replaces
any/Functionwith concrete domain, event, observable, and callback types. - Updates component fixtures and test data.
- Adds typed sidenav selection metadata and accordion parent contracts.
Review findings:
- moderate (1 vote): The action callback type may break callbacks accepting parameters.
- moderate (3 votes): The image error output declares
Eventbut emits no value. - moderate (1 vote): Modal
unknown[]payloads may break consumers expecting known types. - critical (1 vote): Sidenav menu output and parent handler types are incompatible under strict templates.
- moderate (1 vote):
setModelno longer returns the assigned model. - critical (2 votes): Sidenav
unknownoutputs break field access and retain incompatible child bindings.
File summaries
| File | Change |
|---|---|
src/ui-kit/components/tabs/tabs.spec.ts |
Typed the component fixture. |
src/ui-kit/components/sidenav/sidenav/sidenav.spec.ts |
Typed fixtures and menu data. |
src/ui-kit/components/sidenav/sidenav/sidenav.component.ts |
Typed outputs, changes, and model lookup. |
src/ui-kit/components/sidenav/services/sidenav.service.ts |
Typed navigation model state. |
src/ui-kit/components/sidenav/menu-item/menu-item.spec.ts |
Typed fixtures and menu test data. |
src/ui-kit/components/sidenav/menu-item/menu-item.component.ts |
Typed menu outputs and helpers. |
src/ui-kit/components/sidenav/interfaces.ts |
Added typed selection metadata. |
src/ui-kit/components/point-of-contact/point-of-contact.spec.ts |
Typed the fixture. |
src/ui-kit/components/pagination/pagination.spec.ts |
Typed fixture data and options. |
src/ui-kit/components/multiselect-dropdown/multiselect-dropdown.spec.ts |
Typed dropdown options and fixture. |
src/ui-kit/components/multiselect-dropdown/multiselect-dropdown.component.ts |
Typed model and events. |
src/ui-kit/components/modal/modal.component.ts |
Typed modal payloads and helpers. |
src/ui-kit/components/image/image.component.ts |
Typed image events and streams. |
src/ui-kit/components/history/history.spec.ts |
Typed the fixture. |
src/ui-kit/components/header/header.component.ts |
Typed the dropdown output. |
src/ui-kit/components/header/header.component.spec.ts |
Typed the fixture. |
src/ui-kit/components/data-table/sort.directive.spec.ts |
Typed emitted sort values. |
src/ui-kit/components/data-table/data-source.sample.spec.ts |
Typed the sample data source. |
src/ui-kit/components/comments/comments.spec.ts |
Typed service usage and fixture. |
src/ui-kit/components/comments/comments.component.ts |
Typed forms, streams, and comments. |
src/ui-kit/components/comments/comment/comment.spec.ts |
Typed the fixture. |
src/ui-kit/components/comments/comment/comment.component.ts |
Typed the delete output. |
src/ui-kit/components/breadcrumbs/breadcrumbs.spec.ts |
Typed the fixture. |
src/ui-kit/components/breadcrumbs/breadcrumbs.component.ts |
Typed output and subscription. |
src/ui-kit/components/banner/banner.spec.ts |
Typed the fixture. |
src/ui-kit/components/alert/alert.spec.ts |
Typed the fixture. |
src/ui-kit/components/alert/alert.component.ts |
Typed alert outputs and metadata. |
src/ui-kit/components/alert-footer/alert-footer.spec.ts |
Typed fixture and injected service. |
src/ui-kit/components/alert-footer/alert-footer.service.ts |
Typed alert storage and indexing. |
src/ui-kit/components/actions/actions-dropdown/actions-dropdown.spec.ts |
Typed actions and emitted values. |
src/ui-kit/components/actions/actions-dropdown/actions-dropdown.component.ts |
Typed action outputs and DOM references. |
src/ui-kit/components/actions/action-interface.ts |
Replaced the unsafe callback type. |
src/ui-kit/components/actions/action-button/action-button.spec.ts |
Typed fixtures and emitted values. |
src/ui-kit/components/actions/action-button/action-button.component.ts |
Typed action outputs. |
src/ui-kit/components/accordion/accordion.spec.ts |
Typed fixtures and component lookup. |
src/ui-kit/components/accordion/accordion.component.ts |
Added a typed accordion parent contract. |
eslint-baseline.json |
Lowered the root warning baseline. |
Review details
Suppressed comments (4)
src/ui-kit/components/actions/action-interface.ts:5
- Changing this public callback from
Functionto() => unknownis source-breaking for existingSamActionInterfaceconsumers whose callbacks accept parameters, even though the old type allowed them. Since the component invokes callbacks with no arguments, use a safe callable type such as(...args: never[]) => unknown(or a generic callback contract) if those existing function signatures must remain assignable.
callback?: () => unknown;
src/ui-kit/components/modal/modal.component.ts:88
- These public outputs used to expose
any, so existing strict consumers could consume the variadic payload as their known type.unknown[]is not a widening replacement: handlers typed as(args: string[]) => void, or code that readsargs[0]as a string, now fail to compile, making this a source-breaking API change despite the compatibility claim. Preserve a generic/typed payload contract or explicitly coordinate this breaking change.
@Output() onOpen: EventEmitter<unknown[]> = new EventEmitter<unknown[]>();
src/ui-kit/components/sidenav/services/sidenav.service.ts:17
- This removes the value that
setModelreturned at runtime (return (this.model = model)), even though its old annotation saidvoid. Consumers of this public service can observe the assigned model from the actual return value, so this is a behavioral/API regression in a file whose signatures are documented as unchanged. Preserve the return value and reflect it in the return type, or explicitly treat this as a breaking change.
src/ui-kit/components/sidenav/sidenav/sidenav.component.ts:109 - When
listis non-empty but no label matches, the loop falls through and this method returnsundefined. The newnumber[] | falseannotation omits that real result, so callers can rely on an unsound public contract; includeundefinedin the return type or add an explicitreturn falseand handle it consistently.
- Files reviewed: 37/37 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- sidenav/menu-item.component.ts, sidenav/sidenav.component.ts: expose MenuItem (not unknown) on data/selection outputs, matching every real emit site, and align emitChildData()'s parameter type with the child (selection) binding's MenuItem payload so strict Angular template checking no longer sees an Event/MenuItem mismatch. - sidenav/services/sidenav.service.ts: setModel() now returns the assigned MenuItem again (restoring prior runtime behavior), typed precisely instead of reverting to any. - sidenav/sidenav.component.ts: lookupLabelInModel()'s return type widened to number[] | false | undefined to reflect the real fall-through-returns-undefined path instead of silently omitting it. - image/image.component.ts: reverted the error output's payload type to EventEmitter<void> since image.template.html calls error.emit() with no argument; Event was not actually being forwarded. - actions/action-interface.ts: callback? widened to (...args: never[]) => unknown so existing callbacks with parameters remain structurally assignable (source-compatible), rather than the stricter zero-arg () => unknown. - sidenav/sidenav.spec.ts: updated the affected test to emit/assert a MenuItem instead of a bare Event. Verified via npx eslint (0 no-explicit-any/no-unsafe-function-type findings), npx tsc --noEmit (no new errors vs. master), npm --prefix test-app test (155/155 files, 1423/1423 tests), npm run format:check, and both lint:baseline gates.
fpigeonjr
marked this pull request as ready for review
September 11, 2026 14:25
beverly-n
approved these changes
Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replaces unsafe and legacy TypeScript types (
@typescript-eslint/no-explicit-any,no-unsafe-function-type) acrosssrc/ui-kit/components(~112 ESLint findings, 108 in the target rules after excluding 10 unrelated Angular-style findings), as a slice of #586, following the same approach used in #702/#713 (utilities/directives/layout/misc slice).modal/modal.component.ts(15 findings) — typedEventEmitter<any>outputs asEventEmitter<unknown[]>(matching the variadicopenModal(...args)payload), thetypesmap asRecord<string, { class: string; sr?: string }>,_scrollHelpersasReturnType<typeof ScrollHelpers>, andopenModal(...args: any[])→unknown[].comments/comments.component.ts(9) — typed the RxJS observable pipeline (Observable<Event>,Observable<KeyboardEvent>,Observable<Comment[] | Error>) andformasFormGroupinstead ofany; removed a deadevent.keyIdentifiedbranch that noKeyboardEventexposes.sidenav/services/sidenav.service.ts(8) — widenedany/any[]to the existingMenuIteminterface. This file is in the frozenscripts/consumer-deep-imports.jsoncontract, so method signatures are unchanged — only the internal typing is more precise. AddedMenuItem#selection?: number[](previously assigned dynamically via bracket-object mutation) to the interface.sidenav/sidenav/sidenav.component.ts,sidenav/menu-item/menu-item.component.ts(4, 5) — typeddata/selectionoutputs againstMenuItem/unknown,hasChildren()againstMenuIteminstead ofobject+ bracket access,ngOnChangesagainstSimpleChanges.image/image.component.ts(7) — typed observable streams,FileReader#onloadevent, and drag/drop handlers (onDragEnter/onDragOver/onDropEvent) againstDragEvent/ProgressEvent<FileReader>instead ofany.actions/action-interface.ts—callback?: Function→callback?: () => unknown(the oneno-unsafe-function-typefinding);action-button/actions-dropdowncomponents and specs now type theiremitAction/emitCallbackoutputs againstSamActionInterface/unknown.alert/alert.component.ts,alert-footer/alert-footer.service.ts,multiselect-dropdown/multiselect-dropdown.component.ts,breadcrumbs/breadcrumbs.component.ts,accordion/accordion.component.ts,header/header.component.ts— typedEventEmitteroutputs and internal fields against existing types (AlertType,OptionsType,IBreadcrumb) instead ofany;accordion.component.tsintroduces anAccordionParentinterface forSamAccordionSection's parent injection (avoids a decorator-metadata self-reference issue that a directSamAccordionComponenttype caused).comments/comment,header,pagination,sidenav,alert,alert-footer,banner,breadcrumbs,data-table,history,point-of-contact,tabsspec files — replacedany-typed fixtures/locals withComponentFixture<T>and concrete payload types.any/Functionto precise or intentionally-generic (unknown) types.@angular-eslint/no-output-native,no-output-on-prefix,no-empty-lifecycle-method, andno-input-rename— Angular naming-convention lint, not unsafe-type findings.eslint-baseline.json's root warning baseline is lowered924 → 457(measured vianpm run lint:baseline:bump).Motivation and Context
Closes #704
Type of Change (Select One and Apply Label)
bugfixlabelenhancementlabelbreakinglabelmaintenancelabelHow to Test
npm ci && npm ci --prefix test-appnpm run lint:baseline— confirm 457/457 warnings, 0 errorsnpm --prefix test-app run lint:baseline— confirm 0/0 warnings, 0 errorsnpx eslint src/ui-kit/components— confirm 0@typescript-eslint/no-explicit-any/no-unsafe-function-typefindings remain (only the 10 unrelated Angular-style warnings)npx tsc -p tsconfig.json --noEmit— confirm no new compile errors vs.masternpm --prefix test-app test— confirm all 155 spec files / 1423 tests pass, coverage gate passesnpm --prefix test-app run build— confirm the production build still succeedsnpm run format:check— confirm cleanExpected result: All checks pass; ESLint
rootbaseline is457(down from924); noany/Functionusages remain insrc/ui-kit/componentsoutside the 10 documented out-of-scope Angular-style findings.Screenshots (if appropriate)
N/A — type-only changes, no UI/visual impact.
Checklist
gh-<number>-<slug>)format:checkpasses (npm run format:check)lintpasses (npm run lint)buildpasses (cd test-app && npm run build)cd test-app && npm test)