Skip to content

Replace unsafe/legacy types in ui-kit/components - #729

Merged
fpigeonjr merged 2 commits into
masterfrom
gh-704-replace-unsafe-legacy-types-in-ui-kit-components
Sep 11, 2026
Merged

Replace unsafe/legacy types in ui-kit/components#729
fpigeonjr merged 2 commits into
masterfrom
gh-704-replace-unsafe-legacy-types-in-ui-kit-components

Conversation

@fpigeonjr

Copy link
Copy Markdown
Contributor

Description

Replaces unsafe and legacy TypeScript types (@typescript-eslint/no-explicit-any, no-unsafe-function-type) across src/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) — typed EventEmitter<any> outputs as EventEmitter<unknown[]> (matching the variadic openModal(...args) payload), the types map as Record<string, { class: string; sr?: string }>, _scrollHelpers as ReturnType<typeof ScrollHelpers>, and openModal(...args: any[])unknown[].
  • comments/comments.component.ts (9) — typed the RxJS observable pipeline (Observable<Event>, Observable<KeyboardEvent>, Observable<Comment[] | Error>) and form as FormGroup instead of any; removed a dead event.keyIdentified branch that no KeyboardEvent exposes.
  • sidenav/services/sidenav.service.ts (8) — widened any/any[] to the existing MenuItem interface. This file is in the frozen scripts/consumer-deep-imports.json contract, so method signatures are unchanged — only the internal typing is more precise. Added MenuItem#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) — typed data/selection outputs against MenuItem/unknown, hasChildren() against MenuItem instead of object + bracket access, ngOnChanges against SimpleChanges.
  • image/image.component.ts (7) — typed observable streams, FileReader#onload event, and drag/drop handlers (onDragEnter/onDragOver/onDropEvent) against DragEvent/ProgressEvent<FileReader> instead of any.
  • actions/action-interface.tscallback?: Functioncallback?: () => unknown (the one no-unsafe-function-type finding); action-button/actions-dropdown components and specs now type their emitAction/emitCallback outputs against SamActionInterface/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 — typed EventEmitter outputs and internal fields against existing types (AlertType, OptionsType, IBreadcrumb) instead of any; accordion.component.ts introduces an AccordionParent interface for SamAccordionSection's parent injection (avoids a decorator-metadata self-reference issue that a direct SamAccordionComponent type caused).
  • Remaining findings scattered across comments/comment, header, pagination, sidenav, alert, alert-footer, banner, breadcrumbs, data-table, history, point-of-contact, tabs spec files — 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, only widening any/Function to precise or intentionally-generic (unknown) types.
  • Left untouched (out of scope, own issue territory, same boundary Replace unsafe/legacy types in ui-kit utilities, directives, layout, and misc small areas #702 drew): 10 warnings for @angular-eslint/no-output-native, no-output-on-prefix, no-empty-lifecycle-method, and no-input-rename — Angular naming-convention lint, not unsafe-type findings.
  • eslint-baseline.json's root warning baseline is lowered 924 → 457 (measured via npm run lint:baseline:bump).

Motivation and Context

Closes #704

Type of Change (Select One and Apply Label)

  • Bug fix (non-breaking change which fixes an issue) → Apply bugfix label
  • New feature (non-breaking change which adds functionality) → Apply enhancement label
  • Breaking change (fix or feature that would cause existing functionality to change) → Apply breaking label
  • Documentation / configuration update → Apply maintenance label

How to Test

  1. npm ci && npm ci --prefix test-app
  2. npm run lint:baseline — confirm 457/457 warnings, 0 errors
  3. npm --prefix test-app run lint:baseline — confirm 0/0 warnings, 0 errors
  4. npx eslint src/ui-kit/components — confirm 0 @typescript-eslint/no-explicit-any / no-unsafe-function-type findings remain (only the 10 unrelated Angular-style warnings)
  5. npx tsc -p tsconfig.json --noEmit — confirm no new compile errors vs. master
  6. npm --prefix test-app test — confirm all 155 spec files / 1423 tests pass, coverage gate passes
  7. npm --prefix test-app run build — confirm the production build still succeeds
  8. npm run format:check — confirm clean

Expected result: All checks pass; ESLint root baseline is 457 (down from 924); no any/Function usages remain in src/ui-kit/components outside the 10 documented out-of-scope Angular-style findings.

Screenshots (if appropriate)

N/A — type-only changes, no UI/visual impact.

Checklist

  • Branch name follows convention (e.g. gh-<number>-<slug>)
  • PR title starts with a verb in the imperative mood
  • I have self-reviewed my own code
  • format:check passes (npm run format:check)
  • lint passes (npm run lint)
  • build passes (cd test-app && npm run build)
  • Tests pass and coverage is reported (cd test-app && npm test)
  • If this change requires a documentation update, I have updated it accordingly
  • If there are dependent changes, they have been merged and published in downstream modules

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
@fpigeonjr fpigeonjr added enhancement maintenance Repo maintenance / tooling tech-debt Technical debt cleanup work labels Sep 10, 2026
@fpigeonjr fpigeonjr self-assigned this Sep 10, 2026
@fpigeonjr
fpigeonjr requested a lite review from Copilot September 10, 2026 22:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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/Function with 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 Event but 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): setModel no longer returns the assigned model.
  • critical (2 votes): Sidenav unknown outputs 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 Function to () => unknown is source-breaking for existing SamActionInterface consumers 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 reads args[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 setModel returned at runtime (return (this.model = model)), even though its old annotation said void. 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 list is non-empty but no label matches, the loop falls through and this method returns undefined. The new number[] | false annotation omits that real result, so callers can rely on an unsound public contract; include undefined in the return type or add an explicit return false and 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.

Comment thread src/ui-kit/components/sidenav/menu-item/menu-item.component.ts
Comment thread src/ui-kit/components/sidenav/sidenav/sidenav.component.ts Outdated
Comment thread src/ui-kit/components/image/image.component.ts Outdated
- 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
fpigeonjr marked this pull request as ready for review September 11, 2026 14:25
@fpigeonjr
fpigeonjr requested a review from a team as a code owner September 11, 2026 14:25
@fpigeonjr
fpigeonjr merged commit 471a997 into master Sep 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement maintenance Repo maintenance / tooling tech-debt Technical debt cleanup work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace unsafe/legacy types in ui-kit/components

3 participants