@jmruthers/pace-core 0.5.193 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +299 -0
- package/cursor-rules/01-standards-compliance.mdc +244 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +222 -0
- package/cursor-rules/04-testing-standards.mdc +268 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +309 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +119 -0
- package/cursor-rules/README.md +192 -0
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
- package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +15 -12
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +59 -126
- package/dist/hooks.js +19 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +63 -16
- package/dist/index.js +23 -24
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +146 -115
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +15 -15
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +263 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +6 -28
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +40 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +12 -6
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +86 -0
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/install-cursor-rules.cjs +236 -0
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +16 -4
- package/src/components/Button/Button.tsx +27 -4
- package/src/components/Calendar/Calendar.tsx +9 -3
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +40 -6
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +200 -561
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +9 -1
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +9 -1
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +8 -7
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +4 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +15 -12
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +31 -26
- package/src/components/Header/Header.tsx +22 -11
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +36 -34
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +12 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +95 -438
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +5 -1
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +22 -19
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +5 -1
- package/src/hooks/public/usePublicEventLogo.ts +5 -1
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +5 -1
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +16 -2
- package/src/hooks/useFocusTrap.ts +7 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +4 -1
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +8 -1
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +3 -3
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +5 -2
- package/src/rbac/components/PagePermissionGuard.tsx +158 -18
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +6 -2
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +21 -6
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +212 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-BC4IJKSL.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-IIELH4DL.js.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js +0 -412
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E.js +0 -68
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -666
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Import Patterns Check Module
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Audit/Checks/Imports
|
|
7
|
+
*
|
|
8
|
+
* Checks for:
|
|
9
|
+
* - Unused imports from pace-core
|
|
10
|
+
* - Missing default imports
|
|
11
|
+
* - Circular dependencies
|
|
12
|
+
* - Wrong import paths
|
|
13
|
+
* - Barrel import anti-patterns
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { findSourceFiles, getRelativePath } = require('../utils.cjs');
|
|
19
|
+
|
|
20
|
+
const importsCheck = {
|
|
21
|
+
name: 'imports',
|
|
22
|
+
description: 'Import pattern analysis (unused imports, circular dependencies, wrong paths)',
|
|
23
|
+
severity: 'warning',
|
|
24
|
+
|
|
25
|
+
async run(context) {
|
|
26
|
+
const { projectRoot, files } = context;
|
|
27
|
+
const issues = [];
|
|
28
|
+
const warnings = [];
|
|
29
|
+
const suggestions = [];
|
|
30
|
+
|
|
31
|
+
if (!files || files.length === 0) {
|
|
32
|
+
return { issues, warnings, suggestions };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Track imports and usage
|
|
36
|
+
const importMap = new Map(); // file -> { imports: [], exports: [] }
|
|
37
|
+
const usageMap = new Map(); // file -> Set of used identifiers
|
|
38
|
+
|
|
39
|
+
// First pass: collect imports and exports
|
|
40
|
+
for (const filePath of files) {
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
43
|
+
const relativePath = getRelativePath(filePath, projectRoot);
|
|
44
|
+
|
|
45
|
+
const imports = [];
|
|
46
|
+
const exports = [];
|
|
47
|
+
const used = new Set();
|
|
48
|
+
|
|
49
|
+
// Find all imports
|
|
50
|
+
const importPattern = /import\s+(?:(?:\*\s+as\s+(\w+))|(?:{([^}]+)})|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
53
|
+
const namespace = match[1];
|
|
54
|
+
const namedImports = match[2];
|
|
55
|
+
const defaultImport = match[3];
|
|
56
|
+
const modulePath = match[4];
|
|
57
|
+
|
|
58
|
+
imports.push({
|
|
59
|
+
namespace,
|
|
60
|
+
namedImports: namedImports ? namedImports.split(',').map(s => s.trim().replace(/\s+as\s+\w+/, '')) : [],
|
|
61
|
+
defaultImport,
|
|
62
|
+
modulePath,
|
|
63
|
+
line: content.substring(0, match.index).split('\n').length
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Find all exports
|
|
68
|
+
const exportPattern = /export\s+(?:(?:default\s+)?(?:function|const|class|interface|type)\s+(\w+)|(?:{([^}]+)})|(\w+))/g;
|
|
69
|
+
while ((match = exportPattern.exec(content)) !== null) {
|
|
70
|
+
const namedExport = match[1] || match[3];
|
|
71
|
+
const namedExports = match[2];
|
|
72
|
+
|
|
73
|
+
if (namedExport) {
|
|
74
|
+
exports.push(namedExport);
|
|
75
|
+
}
|
|
76
|
+
if (namedExports) {
|
|
77
|
+
exports.push(...namedExports.split(',').map(s => s.trim()));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Find usage of imported identifiers (simplified heuristic)
|
|
82
|
+
imports.forEach(imp => {
|
|
83
|
+
if (imp.defaultImport) {
|
|
84
|
+
// Check if default import is used
|
|
85
|
+
const usagePattern = new RegExp(`\\b${imp.defaultImport}\\b`, 'g');
|
|
86
|
+
if (usagePattern.test(content)) {
|
|
87
|
+
used.add(imp.defaultImport);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (imp.namespace) {
|
|
91
|
+
const usagePattern = new RegExp(`\\b${imp.namespace}\\.`, 'g');
|
|
92
|
+
if (usagePattern.test(content)) {
|
|
93
|
+
used.add(imp.namespace);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
imp.namedImports.forEach(name => {
|
|
97
|
+
const cleanName = name.trim();
|
|
98
|
+
const usagePattern = new RegExp(`\\b${cleanName}\\b`, 'g');
|
|
99
|
+
// Count occurrences - if more than 1, it's likely used (1 is the import itself)
|
|
100
|
+
const matches = content.match(usagePattern);
|
|
101
|
+
if (matches && matches.length > 1) {
|
|
102
|
+
used.add(cleanName);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
importMap.set(relativePath, { imports, exports, content });
|
|
108
|
+
usageMap.set(relativePath, used);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Skip files with errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Second pass: analyze imports
|
|
115
|
+
for (const [filePath, { imports, content }] of importMap.entries()) {
|
|
116
|
+
const relativePath = filePath;
|
|
117
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
118
|
+
|
|
119
|
+
// Skip pace-core package files - imports check is for consuming applications, not the library itself
|
|
120
|
+
// The library must use internal import paths and may have unused imports in examples/config files
|
|
121
|
+
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
122
|
+
if (isPaceCorePackage) {
|
|
123
|
+
continue; // Skip library files
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
127
|
+
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
128
|
+
if (isRootSrc) {
|
|
129
|
+
continue; // Skip demo app files
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Skip scripts directory - utility scripts don't need import validation
|
|
133
|
+
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
134
|
+
if (isScript) {
|
|
135
|
+
continue; // Skip script files
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const used = usageMap.get(filePath) || new Set();
|
|
139
|
+
|
|
140
|
+
imports.forEach(imp => {
|
|
141
|
+
// Check for unused imports from pace-core
|
|
142
|
+
if (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/')) {
|
|
143
|
+
const unused = [];
|
|
144
|
+
|
|
145
|
+
if (imp.defaultImport && !used.has(imp.defaultImport)) {
|
|
146
|
+
unused.push(imp.defaultImport);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
imp.namedImports.forEach(name => {
|
|
150
|
+
if (!used.has(name.trim())) {
|
|
151
|
+
unused.push(name.trim());
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (imp.namespace && !used.has(imp.namespace)) {
|
|
156
|
+
unused.push(`${imp.namespace} (namespace)`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (unused.length > 0) {
|
|
160
|
+
warnings.push({
|
|
161
|
+
type: 'unused-import',
|
|
162
|
+
file: relativePath,
|
|
163
|
+
line: imp.line,
|
|
164
|
+
message: `Unused imports from pace-core: ${unused.join(', ')}`,
|
|
165
|
+
recommendation: `Remove unused imports to reduce bundle size`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check for wrong import paths
|
|
171
|
+
if (imp.modulePath.startsWith('@jmruthers/pace-core/')) {
|
|
172
|
+
const validPaths = [
|
|
173
|
+
'@jmruthers/pace-core',
|
|
174
|
+
'@jmruthers/pace-core/components',
|
|
175
|
+
'@jmruthers/pace-core/hooks',
|
|
176
|
+
'@jmruthers/pace-core/utils',
|
|
177
|
+
'@jmruthers/pace-core/providers',
|
|
178
|
+
'@jmruthers/pace-core/rbac',
|
|
179
|
+
'@jmruthers/pace-core/types'
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
if (!validPaths.includes(imp.modulePath)) {
|
|
183
|
+
warnings.push({
|
|
184
|
+
type: 'wrong-import-path',
|
|
185
|
+
file: relativePath,
|
|
186
|
+
line: imp.line,
|
|
187
|
+
message: `Invalid import path: ${imp.modulePath}`,
|
|
188
|
+
recommendation: `Use one of the valid paths: ${validPaths.join(', ')}`
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for barrel import anti-patterns (importing entire modules)
|
|
194
|
+
if (imp.namespace && (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/'))) {
|
|
195
|
+
warnings.push({
|
|
196
|
+
type: 'barrel-import',
|
|
197
|
+
file: relativePath,
|
|
198
|
+
line: imp.line,
|
|
199
|
+
message: `Namespace import from pace-core: import * as ${imp.namespace}`,
|
|
200
|
+
recommendation: `Import specific exports instead to enable tree-shaking: import { Component1, Component2 } from '@jmruthers/pace-core'`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check for circular dependencies (simplified - check if file A imports B and B imports A)
|
|
207
|
+
const filePaths = Array.from(importMap.keys());
|
|
208
|
+
for (let i = 0; i < filePaths.length; i++) {
|
|
209
|
+
for (let j = i + 1; j < filePaths.length; j++) {
|
|
210
|
+
const fileA = filePaths[i];
|
|
211
|
+
const fileB = filePaths[j];
|
|
212
|
+
const importsA = importMap.get(fileA)?.imports || [];
|
|
213
|
+
const importsB = importMap.get(fileB)?.imports || [];
|
|
214
|
+
|
|
215
|
+
// Check if A imports B
|
|
216
|
+
const aImportsB = importsA.some(imp => {
|
|
217
|
+
const importPath = imp.modulePath;
|
|
218
|
+
return importPath.startsWith('./') || importPath.startsWith('../') ?
|
|
219
|
+
path.resolve(path.dirname(fileA), importPath) === fileB : false;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Check if B imports A
|
|
223
|
+
const bImportsA = importsB.some(imp => {
|
|
224
|
+
const importPath = imp.modulePath;
|
|
225
|
+
return importPath.startsWith('./') || importPath.startsWith('../') ?
|
|
226
|
+
path.resolve(path.dirname(fileB), importPath) === fileA : false;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (aImportsB && bImportsA) {
|
|
230
|
+
issues.push({
|
|
231
|
+
type: 'circular-dependency',
|
|
232
|
+
file: fileA,
|
|
233
|
+
message: `Circular dependency detected between ${fileA} and ${fileB}`,
|
|
234
|
+
recommendation: `Refactor to break the circular dependency by extracting shared code to a third module`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { issues, warnings, suggestions };
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
module.exports = importsCheck;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Performance Check Module
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Audit/Checks/Performance
|
|
7
|
+
*
|
|
8
|
+
* Checks for:
|
|
9
|
+
* - Missing React.memo on expensive components
|
|
10
|
+
* - Missing useMemo/useCallback for expensive computations
|
|
11
|
+
* - Unnecessary re-renders
|
|
12
|
+
* - Large inline objects/functions in JSX props
|
|
13
|
+
* - Missing key props in lists
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const { getRelativePath, getLineNumber } = require('../utils.cjs');
|
|
18
|
+
|
|
19
|
+
const performanceCheck = {
|
|
20
|
+
name: 'performance',
|
|
21
|
+
description: 'Performance anti-patterns (missing memoization, unnecessary re-renders)',
|
|
22
|
+
severity: 'warning',
|
|
23
|
+
|
|
24
|
+
async run(context) {
|
|
25
|
+
const { projectRoot, files } = context;
|
|
26
|
+
const issues = [];
|
|
27
|
+
const warnings = [];
|
|
28
|
+
const suggestions = [];
|
|
29
|
+
|
|
30
|
+
if (!files || files.length === 0) {
|
|
31
|
+
return { issues, warnings, suggestions };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const filePath of files) {
|
|
35
|
+
try {
|
|
36
|
+
// Only check React component files
|
|
37
|
+
if (!filePath.match(/\.(tsx|jsx)$/)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
const relativePath = getRelativePath(filePath, projectRoot);
|
|
43
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
44
|
+
|
|
45
|
+
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
46
|
+
// Note: We DO check packages/core/ files because performance issues (like missing key props) are real issues that should be fixed
|
|
47
|
+
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
48
|
+
if (isRootSrc) {
|
|
49
|
+
continue; // Skip demo app files
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Skip scripts directory - utility scripts don't need performance validation
|
|
53
|
+
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
54
|
+
if (isScript) {
|
|
55
|
+
continue; // Skip script files
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Skip examples directory - these are demo/example files, not production code
|
|
59
|
+
// Examples are meant to show usage patterns, not be optimized for performance
|
|
60
|
+
const isExample = normalizedPath.includes('/examples/') || normalizedPath.startsWith('examples/');
|
|
61
|
+
if (isExample) {
|
|
62
|
+
continue; // Skip example files
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Skip test files - test code doesn't need performance optimization
|
|
66
|
+
const isTestFile = normalizedPath.includes('.test.') ||
|
|
67
|
+
normalizedPath.includes('.spec.') ||
|
|
68
|
+
normalizedPath.includes('test-setup') ||
|
|
69
|
+
normalizedPath.includes('__tests__');
|
|
70
|
+
if (isTestFile) {
|
|
71
|
+
continue; // Skip test files
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// For library code (packages/core/src), be more lenient with performance suggestions
|
|
75
|
+
// Library components often use inline functions which are acceptable patterns
|
|
76
|
+
const isLibraryCode = normalizedPath.includes('packages/core/src/');
|
|
77
|
+
|
|
78
|
+
// Check for components that could benefit from React.memo
|
|
79
|
+
// Note: For library components, React.memo should be used judiciously
|
|
80
|
+
// We only suggest it for components that are likely to render frequently
|
|
81
|
+
const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
|
|
82
|
+
let match;
|
|
83
|
+
while ((match = componentPattern.exec(content)) !== null) {
|
|
84
|
+
const componentName = match[3];
|
|
85
|
+
const isMemoized = content.includes(`React.memo(${componentName})`) ||
|
|
86
|
+
content.includes(`memo(${componentName})`) ||
|
|
87
|
+
content.includes(`export default memo(${componentName})`);
|
|
88
|
+
|
|
89
|
+
// Check if component receives props and is not memoized
|
|
90
|
+
if (match[0].includes('props') || match[0].includes('{') || content.includes(`${componentName}({`)) {
|
|
91
|
+
// Check if component has significant JSX (heuristic: more than 5 lines of JSX)
|
|
92
|
+
const componentStart = match.index;
|
|
93
|
+
const afterMatch = content.substring(componentStart);
|
|
94
|
+
const jsxLines = (afterMatch.match(/<[A-Z]/g) || []).length;
|
|
95
|
+
|
|
96
|
+
// Skip if component uses forwardRef (memoization handled differently)
|
|
97
|
+
// Skip if component is a hook (starts with 'use')
|
|
98
|
+
// Skip if component is very simple (less than 5 JSX elements)
|
|
99
|
+
// Skip if component is already memoized
|
|
100
|
+
if (jsxLines > 5 && !isMemoized && !content.includes('forwardRef') && !componentName.startsWith('use')) {
|
|
101
|
+
// For library components, be more conservative - only suggest for components
|
|
102
|
+
// that are likely to be in lists or render frequently
|
|
103
|
+
// Skip if it's a page-level component (like *Page, *Layout, *Route)
|
|
104
|
+
const isPageComponent = /Page|Layout|Route|Modal|Dialog/.test(componentName);
|
|
105
|
+
|
|
106
|
+
// Only suggest memo for components that are likely to render frequently
|
|
107
|
+
// (not page-level components, which typically render once per route)
|
|
108
|
+
// For library code, skip React.memo suggestions entirely - memoization decisions
|
|
109
|
+
// should be made by the consuming application based on their specific use case
|
|
110
|
+
if (!isPageComponent && !isLibraryCode) {
|
|
111
|
+
suggestions.push({
|
|
112
|
+
type: 'missing-memo',
|
|
113
|
+
file: relativePath,
|
|
114
|
+
line: getLineNumber(content, match.index),
|
|
115
|
+
message: `Component '${componentName}' receives props but is not memoized`,
|
|
116
|
+
recommendation: `Consider wrapping with React.memo if the component renders frequently: export default React.memo(${componentName})`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// For library code, skip React.memo suggestions - library components are often
|
|
120
|
+
// controlled by parent components, and memoization should be decided by consumers
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check for large inline objects/functions in JSX props
|
|
126
|
+
// But exclude JSX children (which is standard React pattern)
|
|
127
|
+
const jsxPropPattern = /<[A-Z]\w+\s+[^>]*\{[^}]{100,}[^>]*>/g;
|
|
128
|
+
let jsxMatch;
|
|
129
|
+
while ((jsxMatch = jsxPropPattern.exec(content)) !== null) {
|
|
130
|
+
const matchText = jsxMatch[0];
|
|
131
|
+
|
|
132
|
+
// Check if this is JSX children (element prop) or guard component props
|
|
133
|
+
// Patterns to exclude (these are valid React patterns):
|
|
134
|
+
// 1. <Route element={<Component />} /> - React Router pattern
|
|
135
|
+
// 2. <GuardComponent fallback={<Component />} /> - Guard component pattern
|
|
136
|
+
// 3. <Component>{children}</Component> - JSX children (but this won't match our pattern)
|
|
137
|
+
// 4. Any prop that contains JSX elements (starts with <)
|
|
138
|
+
|
|
139
|
+
const isRouteElement = /element\s*=\s*\{/.test(matchText);
|
|
140
|
+
const isGuardProp = /(fallback|loading|error|children)\s*=\s*\{/.test(matchText);
|
|
141
|
+
const hasJSXElement = /<[A-Z]\w+/.test(matchText);
|
|
142
|
+
|
|
143
|
+
// Also check if it's a className utility call (like cn()) - not a performance issue
|
|
144
|
+
const isUtilityCall = /cn\s*\(|clsx\s*\(|classnames\s*\(/.test(matchText);
|
|
145
|
+
|
|
146
|
+
// Only flag if it's an actual inline object/function, not JSX children
|
|
147
|
+
if (!isRouteElement && !isGuardProp && !hasJSXElement && !isUtilityCall) {
|
|
148
|
+
warnings.push({
|
|
149
|
+
type: 'inline-object-in-jsx',
|
|
150
|
+
file: relativePath,
|
|
151
|
+
line: getLineNumber(content, jsxMatch.index),
|
|
152
|
+
message: 'Large inline object or function in JSX props detected',
|
|
153
|
+
recommendation: 'Extract inline objects/functions to variables or useMemo/useCallback to prevent unnecessary re-renders. JSX children in props (like React Router element prop or guard component fallback prop) are standard React patterns and not performance issues.'
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check for missing key props in lists
|
|
159
|
+
// Only flag if JSX is returned, not data arrays
|
|
160
|
+
const mapPattern = /\.map\s*\(\s*\([^)]+\)\s*=>/g;
|
|
161
|
+
while ((match = mapPattern.exec(content)) !== null) {
|
|
162
|
+
const afterMap = content.substring(match.index, match.index + 500);
|
|
163
|
+
// Check if JSX is returned without key
|
|
164
|
+
// Also check if it's actually returning JSX (not just data objects)
|
|
165
|
+
const returnsJSX = afterMap.includes('return') && afterMap.includes('<');
|
|
166
|
+
|
|
167
|
+
// Check for key in various formats: key=, key:, or inside React.cloneElement
|
|
168
|
+
const hasKey = afterMap.includes('key=') ||
|
|
169
|
+
/key\s*:/.test(afterMap) ||
|
|
170
|
+
/React\.cloneElement\s*\([^,]+,\s*\{[^}]*key\s*:/.test(afterMap);
|
|
171
|
+
|
|
172
|
+
// Check if it's a data transformation (returns object/array/primitive, not JSX)
|
|
173
|
+
// Pattern 1: Returns a simple value like .map(([key, _]) => key)
|
|
174
|
+
const returnsSimpleValue = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*;/.test(afterMap) && !afterMap.includes('<');
|
|
175
|
+
// Pattern 2: Returns a data object without JSX
|
|
176
|
+
const returnsDataObject = /return\s*\{[^<]*\}/.test(afterMap) && !afterMap.match(/return\s*\{[^<]*<[A-Z]/);
|
|
177
|
+
// Pattern 3: Returns array element access like .map((subRow) => subRow.original)
|
|
178
|
+
const returnsDataProperty = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\.[a-zA-Z_$][a-zA-Z0-9_$]*\s*[;}]/.test(afterMap) && !afterMap.includes('<');
|
|
179
|
+
|
|
180
|
+
// Skip pace-core files for this check - library code has complex patterns that are hard to detect accurately
|
|
181
|
+
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
182
|
+
|
|
183
|
+
if (returnsJSX && !hasKey && !returnsDataObject && !returnsSimpleValue && !returnsDataProperty && !isPaceCorePackage) {
|
|
184
|
+
warnings.push({
|
|
185
|
+
type: 'missing-key',
|
|
186
|
+
file: relativePath,
|
|
187
|
+
line: getLineNumber(content, match.index),
|
|
188
|
+
message: 'Array map returns JSX without key prop',
|
|
189
|
+
recommendation: 'Add a unique key prop to list items: {items.map(item => <Item key={item.id} ... />)}. Data array transformations (not JSX) do not need keys.'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check for expensive computations that could use useMemo
|
|
195
|
+
const expensivePatterns = [
|
|
196
|
+
/\.filter\([^)]*\)\.map\(/g, // filter().map() chains
|
|
197
|
+
/\.sort\([^)]*\)\.map\(/g, // sort().map() chains
|
|
198
|
+
/\.reduce\([^)]*\)/g // reduce operations
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
expensivePatterns.forEach(pattern => {
|
|
202
|
+
let expMatch;
|
|
203
|
+
while ((expMatch = pattern.exec(content)) !== null) {
|
|
204
|
+
// Check a larger context to see if it's already in useMemo or useCallback
|
|
205
|
+
const beforeMatch = content.substring(Math.max(0, expMatch.index - 500), expMatch.index);
|
|
206
|
+
const afterMatch = content.substring(expMatch.index, Math.min(content.length, expMatch.index + 200));
|
|
207
|
+
|
|
208
|
+
// Check if it's already in useMemo or useCallback
|
|
209
|
+
if (beforeMatch.includes('useMemo') || beforeMatch.includes('useCallback')) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check if it's inside a function definition that's static (like aggregateFn, cell renderer, etc.)
|
|
214
|
+
// These are function definitions that are called by the library, not recalculated on every render
|
|
215
|
+
const isStaticFunctionDefinition = /(aggregateFn|cell|header|footer|render|format|transform)\s*[:=]\s*\(/.test(beforeMatch);
|
|
216
|
+
if (isStaticFunctionDefinition) {
|
|
217
|
+
continue; // Skip static function definitions
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check if it's inside a const/let declaration that's likely already memoized
|
|
221
|
+
// Pattern: const x = useMemo(() => ... or const x = useCallback(() => ...
|
|
222
|
+
const isInMemoizedConst = /const\s+\w+\s*=\s*(useMemo|useCallback)\s*\(/.test(beforeMatch);
|
|
223
|
+
if (isInMemoizedConst) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check if it's part of a column definition (static, not recalculated)
|
|
228
|
+
const isColumnDefinition = /(accessorKey|header|cell|aggregateFn|aggregateCell)\s*:/.test(beforeMatch);
|
|
229
|
+
if (isColumnDefinition) {
|
|
230
|
+
continue; // Skip column definitions - these are static
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
suggestions.push({
|
|
234
|
+
type: 'expensive-computation',
|
|
235
|
+
file: relativePath,
|
|
236
|
+
line: getLineNumber(content, expMatch.index),
|
|
237
|
+
message: 'Expensive computation detected that could benefit from useMemo',
|
|
238
|
+
recommendation: 'Wrap expensive computations in useMemo to prevent recalculation on every render'
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Check for functions passed as props that could use useCallback
|
|
244
|
+
// Only flag complex inline functions, not simple event handlers
|
|
245
|
+
// For library code, be very lenient - inline functions are often acceptable patterns
|
|
246
|
+
if (!isLibraryCode) {
|
|
247
|
+
// Only check for consuming applications, not library code
|
|
248
|
+
const functionPropPattern = /(onClick|onChange|onSubmit|onFocus|onBlur|onMouseEnter|onMouseLeave|onValueChange)\s*=\s*\{[^}]*=>/g;
|
|
249
|
+
let funcMatch;
|
|
250
|
+
while ((funcMatch = functionPropPattern.exec(content)) !== null) {
|
|
251
|
+
const beforeMatch = content.substring(Math.max(0, funcMatch.index - 150), funcMatch.index);
|
|
252
|
+
const afterMatch = content.substring(funcMatch.index, Math.min(content.length, funcMatch.index + 300));
|
|
253
|
+
|
|
254
|
+
// Skip if already using useCallback or defined as const
|
|
255
|
+
if (beforeMatch.includes('useCallback') || beforeMatch.includes('const ')) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Extract the function body to check complexity
|
|
260
|
+
// Handle both arrow functions with and without braces
|
|
261
|
+
let funcBody = '';
|
|
262
|
+
const arrowMatch = afterMatch.match(/=>\s*(\{?)([^}]*?)(\}?)/);
|
|
263
|
+
if (arrowMatch) {
|
|
264
|
+
if (arrowMatch[1] === '{') {
|
|
265
|
+
// Multi-line function with braces - extract content between braces
|
|
266
|
+
const braceMatch = afterMatch.match(/=>\s*\{([^}]+)\}/);
|
|
267
|
+
if (braceMatch) {
|
|
268
|
+
funcBody = braceMatch[1];
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Single expression arrow function
|
|
272
|
+
funcBody = arrowMatch[2];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (funcBody) {
|
|
277
|
+
const trimmedBody = funcBody.trim();
|
|
278
|
+
|
|
279
|
+
// Very short handlers are usually simple
|
|
280
|
+
if (trimmedBody.length < 50) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check for simple setState patterns (even with object spread)
|
|
285
|
+
const isSimpleSetState = /^\s*set\w+\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody) ||
|
|
286
|
+
/^\s*set\w+\s*\([^)]*prev\s*=>\s*\(\{[^}]*\}\)[^}]*\)\s*;?\s*$/.test(trimmedBody);
|
|
287
|
+
|
|
288
|
+
// Check for simple function calls
|
|
289
|
+
const isSimpleFunctionCall = /^\s*(handle\w+|on\w+|navigate|window\.|document\.)\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody);
|
|
290
|
+
|
|
291
|
+
// Check for simple conditional (single if/ternary)
|
|
292
|
+
const isSimpleConditional = /^\s*(if\s*\([^)]+\)\s*\{[^}]{0,50}\}|[^?]+\?[^:]+:[^;]+;?)\s*$/.test(trimmedBody);
|
|
293
|
+
|
|
294
|
+
// Check for simple value transformations (common in library components)
|
|
295
|
+
// Pattern: (e) => handleFunc(e.target.value || undefined)
|
|
296
|
+
// Pattern: (e) => handleFunc(e.target.value ? transform(e.target.value) : undefined)
|
|
297
|
+
const isSimpleValueTransform = /^\s*\w+\s*\([^)]*\.(target|value|checked|selected)[^)]*\)\s*;?\s*$/.test(trimmedBody) ||
|
|
298
|
+
/^\s*\w+\s*\([^)]*\?[^:]+:[^;]+\)\s*;?\s*$/.test(trimmedBody);
|
|
299
|
+
|
|
300
|
+
// Skip simple handlers - they're fine as inline functions
|
|
301
|
+
if (isSimpleSetState || isSimpleFunctionCall || isSimpleConditional || isSimpleValueTransform) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
suggestions.push({
|
|
307
|
+
type: 'inline-function-prop',
|
|
308
|
+
file: relativePath,
|
|
309
|
+
line: getLineNumber(content, funcMatch.index),
|
|
310
|
+
message: 'Complex inline function passed as prop',
|
|
311
|
+
recommendation: 'Use useCallback to memoize complex functions passed as props to prevent child re-renders. Simple event handlers (single setState call or function invocation) are fine as inline functions.'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// For library code, skip this check entirely - inline functions are acceptable patterns
|
|
316
|
+
} catch (error) {
|
|
317
|
+
// Skip files with errors
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { issues, warnings, suggestions };
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
module.exports = performanceCheck;
|