@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,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Form Validation Check Module
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Audit/Checks/Forms
|
|
7
|
+
*
|
|
8
|
+
* Checks for:
|
|
9
|
+
* - Missing form validation
|
|
10
|
+
* - Incorrect useZodForm usage
|
|
11
|
+
* - Missing error messages
|
|
12
|
+
* - Form submission without loading states
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const { getRelativePath, getLineNumber } = require('../utils.cjs');
|
|
17
|
+
|
|
18
|
+
const formsCheck = {
|
|
19
|
+
name: 'forms',
|
|
20
|
+
description: 'Form validation patterns (useZodForm, error handling)',
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
|
|
23
|
+
async run(context) {
|
|
24
|
+
const { projectRoot, files } = context;
|
|
25
|
+
const issues = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const suggestions = [];
|
|
28
|
+
|
|
29
|
+
if (!files || files.length === 0) {
|
|
30
|
+
return { issues, warnings, suggestions };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const filePath of files) {
|
|
34
|
+
try {
|
|
35
|
+
// Only check React component files
|
|
36
|
+
if (!filePath.match(/\.(tsx|jsx)$/)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
41
|
+
const relativePath = getRelativePath(filePath, projectRoot);
|
|
42
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
43
|
+
|
|
44
|
+
// Skip pace-core package files - forms check is for consuming applications, not the library itself
|
|
45
|
+
// Note: Library components (like Form.tsx, LoginForm.tsx, Select.tsx) ARE the Form components
|
|
46
|
+
// They don't need to use themselves - they provide the form functionality
|
|
47
|
+
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
48
|
+
if (isPaceCorePackage) {
|
|
49
|
+
continue; // Skip library files (including examples)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
53
|
+
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
54
|
+
if (isRootSrc) {
|
|
55
|
+
continue; // Skip demo app files
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Skip scripts directory - utility scripts don't need form validation
|
|
59
|
+
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
60
|
+
if (isScript) {
|
|
61
|
+
continue; // Skip script files
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for form elements
|
|
65
|
+
const hasForm = /<form|Form|useZodForm/.test(content);
|
|
66
|
+
if (!hasForm) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check for useZodForm usage
|
|
71
|
+
if (content.includes('useZodForm')) {
|
|
72
|
+
const zodFormPattern = /const\s+[^=]+=\s+useZodForm\s*\(/g;
|
|
73
|
+
let formMatch;
|
|
74
|
+
while ((formMatch = zodFormPattern.exec(content)) !== null) {
|
|
75
|
+
const afterForm = content.substring(formMatch.index, Math.min(content.length, formMatch.index + 500));
|
|
76
|
+
|
|
77
|
+
// Check if schema is provided
|
|
78
|
+
if (!afterForm.includes('schema:') && !afterForm.includes('schema =')) {
|
|
79
|
+
warnings.push({
|
|
80
|
+
type: 'missing-form-schema',
|
|
81
|
+
file: relativePath,
|
|
82
|
+
line: getLineNumber(content, formMatch.index),
|
|
83
|
+
message: 'useZodForm called without schema',
|
|
84
|
+
recommendation: 'Provide a Zod schema to useZodForm for form validation'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if form errors are displayed
|
|
89
|
+
const hasErrorDisplay = /formState\.errors|errors\[|\.error/.test(content);
|
|
90
|
+
if (!hasErrorDisplay) {
|
|
91
|
+
suggestions.push({
|
|
92
|
+
type: 'missing-form-errors',
|
|
93
|
+
file: relativePath,
|
|
94
|
+
line: getLineNumber(content, formMatch.index),
|
|
95
|
+
message: 'Form may not be displaying validation errors',
|
|
96
|
+
recommendation: 'Display form errors to users using formState.errors or FormField error prop'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for form submission without loading state
|
|
103
|
+
const onSubmitPattern = /onSubmit\s*=\s*\{[^}]*async/g;
|
|
104
|
+
let submitMatch;
|
|
105
|
+
while ((submitMatch = onSubmitPattern.exec(content)) !== null) {
|
|
106
|
+
const afterSubmit = content.substring(submitMatch.index, Math.min(content.length, submitMatch.index + 1000));
|
|
107
|
+
|
|
108
|
+
// Check if there's a loading state
|
|
109
|
+
const hasLoadingState = /isLoading|loading|isSubmitting|submitting/.test(afterSubmit);
|
|
110
|
+
const hasDisabled = /disabled\s*=\s*\{/.test(content);
|
|
111
|
+
|
|
112
|
+
if (!hasLoadingState && !hasDisabled) {
|
|
113
|
+
suggestions.push({
|
|
114
|
+
type: 'missing-submit-loading',
|
|
115
|
+
file: relativePath,
|
|
116
|
+
line: getLineNumber(content, submitMatch.index),
|
|
117
|
+
message: 'Form submission without loading state',
|
|
118
|
+
recommendation: 'Add loading state during form submission to prevent double-submission and provide user feedback'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for native form elements that should use pace-core Form
|
|
124
|
+
const nativeFormPattern = /<form[^>]*>/g;
|
|
125
|
+
let nativeMatch;
|
|
126
|
+
while ((nativeMatch = nativeFormPattern.exec(content)) !== null) {
|
|
127
|
+
const beforeMatch = content.substring(Math.max(0, nativeMatch.index - 100), nativeMatch.index);
|
|
128
|
+
const usesPaceCoreForm = beforeMatch.includes('from \'@jmruthers/pace-core\'') ||
|
|
129
|
+
content.includes('<Form');
|
|
130
|
+
|
|
131
|
+
if (!usesPaceCoreForm) {
|
|
132
|
+
suggestions.push({
|
|
133
|
+
type: 'native-form-element',
|
|
134
|
+
file: relativePath,
|
|
135
|
+
line: getLineNumber(content, nativeMatch.index),
|
|
136
|
+
message: 'Native <form> element detected',
|
|
137
|
+
recommendation: 'Use Form component from @jmruthers/pace-core for consistent styling and validation'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check for input elements without validation
|
|
143
|
+
const inputPattern = /<input[^>]*>/g;
|
|
144
|
+
let inputMatch;
|
|
145
|
+
while ((inputMatch = inputPattern.exec(content)) !== null) {
|
|
146
|
+
const inputTag = inputMatch[0];
|
|
147
|
+
const hasFormField = content.substring(Math.max(0, inputMatch.index - 200), inputMatch.index).includes('FormField');
|
|
148
|
+
const hasValidation = inputTag.includes('required') ||
|
|
149
|
+
inputTag.includes('pattern') ||
|
|
150
|
+
content.substring(Math.max(0, inputMatch.index - 200), inputMatch.index).includes('register');
|
|
151
|
+
|
|
152
|
+
if (!hasFormField && !hasValidation && !inputTag.includes('type="hidden"')) {
|
|
153
|
+
suggestions.push({
|
|
154
|
+
type: 'unvalidated-input',
|
|
155
|
+
file: relativePath,
|
|
156
|
+
line: getLineNumber(content, inputMatch.index),
|
|
157
|
+
message: 'Input element without validation',
|
|
158
|
+
recommendation: 'Use FormField from @jmruthers/pace-core or add validation to form inputs'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Skip files with errors
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { issues, warnings, suggestions };
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
module.exports = formsCheck;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Heuristic Checks Module
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Audit/Checks/Heuristics
|
|
7
|
+
*
|
|
8
|
+
* Code quality heuristics including:
|
|
9
|
+
* - Large files
|
|
10
|
+
* - God objects (files with many exports)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const heuristicsCheck = {
|
|
17
|
+
name: 'heuristics',
|
|
18
|
+
description: 'Code quality heuristics (large files, god objects)',
|
|
19
|
+
severity: 'suggestion',
|
|
20
|
+
|
|
21
|
+
async run(context) {
|
|
22
|
+
const { projectRoot, files } = context;
|
|
23
|
+
const suggestions = [];
|
|
24
|
+
|
|
25
|
+
if (!files || files.length === 0) {
|
|
26
|
+
return { issues: [], warnings: [], suggestions: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const maxLines = 1000;
|
|
30
|
+
|
|
31
|
+
// Check for large files
|
|
32
|
+
for (const filePath of files) {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
35
|
+
const lines = content.split('\n').length;
|
|
36
|
+
|
|
37
|
+
if (lines > maxLines) {
|
|
38
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
39
|
+
suggestions.push({
|
|
40
|
+
type: 'large-file',
|
|
41
|
+
file: relativePath,
|
|
42
|
+
message: `File has ${lines} lines, consider splitting into smaller modules`,
|
|
43
|
+
recommendation: `Split this file into smaller, focused modules (target: <${maxLines} lines)`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check for god objects (files with many exports)
|
|
48
|
+
const exportCount = (content.match(/export\s+(const|function|class|interface|type)/g) || []).length;
|
|
49
|
+
|
|
50
|
+
if (exportCount > 10) {
|
|
51
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
52
|
+
suggestions.push({
|
|
53
|
+
type: 'god-object',
|
|
54
|
+
file: relativePath,
|
|
55
|
+
message: `File exports ${exportCount} items, consider splitting into smaller modules`,
|
|
56
|
+
recommendation: `Split this file into smaller modules, each with a focused responsibility (target: <10 exports per file)`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Skip files we can't read
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { issues: [], warnings: [], suggestions };
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = heuristicsCheck;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* React Hooks Compliance Check Module
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Audit/Checks/Hooks
|
|
7
|
+
*
|
|
8
|
+
* Checks for:
|
|
9
|
+
* - Hooks called conditionally or after early returns
|
|
10
|
+
* - Hooks called in loops
|
|
11
|
+
* - Missing dependencies in useEffect/useMemo/useCallback
|
|
12
|
+
* - Hooks called in wrong order
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const { getRelativePath, getLineNumber } = require('../utils.cjs');
|
|
17
|
+
|
|
18
|
+
const hooksCheck = {
|
|
19
|
+
name: 'hooks',
|
|
20
|
+
description: 'React hooks compliance (conditional calls, missing dependencies, etc.)',
|
|
21
|
+
severity: 'error',
|
|
22
|
+
|
|
23
|
+
async run(context) {
|
|
24
|
+
const { projectRoot, files } = context;
|
|
25
|
+
const issues = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const suggestions = [];
|
|
28
|
+
|
|
29
|
+
if (!files || files.length === 0) {
|
|
30
|
+
return { issues, warnings, suggestions };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// React hooks that must follow rules
|
|
34
|
+
// Separate actual React hooks from custom hooks
|
|
35
|
+
const actualReactHooks = [
|
|
36
|
+
'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback',
|
|
37
|
+
'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect',
|
|
38
|
+
'useDebugValue'
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const customHooks = [
|
|
42
|
+
'useUnifiedAuth', 'useOrganisations', 'useEvents',
|
|
43
|
+
'usePermissions', 'useCan', 'useSecureSupabase', 'useToast',
|
|
44
|
+
'useDebounce', 'useZodForm', 'useFileReference', 'useRBAC'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// Combined list for pattern matching
|
|
48
|
+
const hookNames = [...actualReactHooks, ...customHooks];
|
|
49
|
+
|
|
50
|
+
const hookPattern = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
|
|
51
|
+
|
|
52
|
+
for (const filePath of files) {
|
|
53
|
+
try {
|
|
54
|
+
// Only check React component files
|
|
55
|
+
if (!filePath.match(/\.(tsx|jsx)$/)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
60
|
+
const relativePath = getRelativePath(filePath, projectRoot);
|
|
61
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
62
|
+
|
|
63
|
+
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
64
|
+
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
65
|
+
if (isRootSrc) {
|
|
66
|
+
continue; // Skip demo app files
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Skip scripts directory - utility scripts don't need hooks validation
|
|
70
|
+
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
71
|
+
if (isScript) {
|
|
72
|
+
continue; // Skip script files
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if this is a pace-core package file
|
|
76
|
+
// Skip "hook-after-return" check for pace-core files - these are false positives.
|
|
77
|
+
// The detection logic has issues with complex nested structures in library code.
|
|
78
|
+
// Other hook checks (conditional calls, missing dependencies) still apply to pace-core.
|
|
79
|
+
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
80
|
+
|
|
81
|
+
// Check for hooks
|
|
82
|
+
if (!hookPattern.test(content)) {
|
|
83
|
+
continue; // No hooks in this file
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find all hook calls
|
|
87
|
+
const hookCalls = [];
|
|
88
|
+
let match;
|
|
89
|
+
const regex = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
|
|
90
|
+
while ((match = regex.exec(content)) !== null) {
|
|
91
|
+
hookCalls.push({
|
|
92
|
+
name: match[1],
|
|
93
|
+
index: match.index,
|
|
94
|
+
line: getLineNumber(content, match.index)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (hookCalls.length === 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for hooks called conditionally
|
|
103
|
+
for (const hookCall of hookCalls) {
|
|
104
|
+
const beforeHook = content.substring(0, hookCall.index);
|
|
105
|
+
const linesBefore = beforeHook.split('\n');
|
|
106
|
+
const currentLine = linesBefore[linesBefore.length - 1];
|
|
107
|
+
|
|
108
|
+
// Check if hook is in a conditional
|
|
109
|
+
const isInConditional = /if\s*\(|else\s*\{|switch\s*\(|case\s+.*:|for\s*\(|while\s*\(|\.map\s*\(/.test(currentLine);
|
|
110
|
+
|
|
111
|
+
if (isInConditional) {
|
|
112
|
+
issues.push({
|
|
113
|
+
type: 'hook-in-conditional',
|
|
114
|
+
file: relativePath,
|
|
115
|
+
line: hookCall.line,
|
|
116
|
+
message: `Hook '${hookCall.name}' is called conditionally or in a loop`,
|
|
117
|
+
recommendation: 'Hooks must be called at the top level of the component, not conditionally or in loops'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if there's an early return before this hook at the component level
|
|
122
|
+
// Only flag returns at the component function level, not inside nested functions
|
|
123
|
+
const functionStart = beforeHook.lastIndexOf('function') > beforeHook.lastIndexOf('=>') ?
|
|
124
|
+
beforeHook.lastIndexOf('function') : beforeHook.lastIndexOf('=>');
|
|
125
|
+
|
|
126
|
+
if (functionStart !== -1) {
|
|
127
|
+
const functionBody = content.substring(functionStart, hookCall.index);
|
|
128
|
+
|
|
129
|
+
// Find all return statements and check if they're at component level (not in nested functions)
|
|
130
|
+
// Only flag CONDITIONAL early returns (guard clauses), not the final return statement
|
|
131
|
+
const returnPattern = /\breturn\s+[^;]+;|\breturn\s*;|\breturn\s+\(/g;
|
|
132
|
+
let returnMatch;
|
|
133
|
+
let hasComponentLevelReturn = false;
|
|
134
|
+
|
|
135
|
+
while ((returnMatch = returnPattern.exec(functionBody)) !== null) {
|
|
136
|
+
const returnIndex = returnMatch.index;
|
|
137
|
+
const beforeReturn = functionBody.substring(Math.max(0, returnIndex - 200), returnIndex);
|
|
138
|
+
const afterReturn = functionBody.substring(returnIndex, Math.min(functionBody.length, returnIndex + 100));
|
|
139
|
+
|
|
140
|
+
// Check if this is a CONDITIONAL early return (guard clause)
|
|
141
|
+
// Pattern: if (...) return ...; or if (...) { return ...; }
|
|
142
|
+
// NOT the final return statement of the component
|
|
143
|
+
const isConditionalReturn = /\bif\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
|
|
144
|
+
/\belse\s+if\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
|
|
145
|
+
/\belse\s*{\s*return/.test(beforeReturn) ||
|
|
146
|
+
/\?\s*\([^)]*\)\s*=>\s*{?\s*return/.test(beforeReturn); // Ternary operator
|
|
147
|
+
|
|
148
|
+
// If it's not a conditional return, it's likely the final return - skip it
|
|
149
|
+
if (!isConditionalReturn) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if return is inside a nested function by looking for patterns
|
|
154
|
+
const isInHookInitializer = /useState\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
155
|
+
const isInEffectCallback = /useEffect\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
156
|
+
const isInQueryFn = /queryFn\s*:\s*\([^)]*\)\s*=>|useQuery\s*\(\s*\{[^}]*queryFn/.test(beforeReturn);
|
|
157
|
+
const isInArrayMethod = /\.(map|filter|find|reduce|forEach|some|every)\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
158
|
+
const isInIIFE = /\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
159
|
+
const isInFunctionExpr = /\bfunction\s*\([^)]*\)\s*\{/.test(beforeReturn);
|
|
160
|
+
const isInUseMemo = /useMemo\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
161
|
+
const isInUseCallback = /useCallback\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
162
|
+
|
|
163
|
+
// Check if return is inside a switch case (also nested)
|
|
164
|
+
const isInSwitchCase = /\bcase\s+[^:]+:\s*[\s\S]*?return/.test(beforeReturn);
|
|
165
|
+
|
|
166
|
+
// If return is NOT in any nested function pattern, it's a component-level conditional return
|
|
167
|
+
if (!isInHookInitializer && !isInEffectCallback && !isInQueryFn &&
|
|
168
|
+
!isInArrayMethod && !isInIIFE && !isInFunctionExpr &&
|
|
169
|
+
!isInUseMemo && !isInUseCallback && !isInSwitchCase) {
|
|
170
|
+
hasComponentLevelReturn = true;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (hasComponentLevelReturn && !isPaceCorePackage) {
|
|
176
|
+
// Skip this check for pace-core files - false positives due to complex nested structures
|
|
177
|
+
issues.push({
|
|
178
|
+
type: 'hook-after-return',
|
|
179
|
+
file: relativePath,
|
|
180
|
+
line: hookCall.line,
|
|
181
|
+
message: `Hook '${hookCall.name}' is called after an early return`,
|
|
182
|
+
recommendation: 'All hooks must be called before any conditional returns at the component level. Move hooks to the top of the component, before any component-level conditional returns. Returns inside nested functions (useState initializers, useEffect callbacks, etc.) are fine. If you have guard clauses (if (loading) return <Loading />), move all hooks above these guard clauses.'
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check for missing dependencies in useEffect, useMemo, useCallback
|
|
189
|
+
const dependencyHooks = ['useEffect', 'useMemo', 'useCallback', 'useLayoutEffect'];
|
|
190
|
+
dependencyHooks.forEach(hookName => {
|
|
191
|
+
// Match hook with dependency array - handle multiline and various formats
|
|
192
|
+
const hookRegex = new RegExp(`${hookName}\\s*\\(([^)]*)\\)\\s*,\\s*\\[([^\\]]*)\\]`, 'gs');
|
|
193
|
+
let depMatch;
|
|
194
|
+
while ((depMatch = hookRegex.exec(content)) !== null) {
|
|
195
|
+
const hookBody = depMatch[1]; // The function body or callback
|
|
196
|
+
const deps = depMatch[2].trim();
|
|
197
|
+
|
|
198
|
+
// If dependency array is empty, check if hook only uses stable globals
|
|
199
|
+
if (deps === '') {
|
|
200
|
+
// Extract the actual callback body (handle arrow functions and regular functions)
|
|
201
|
+
let callbackBody = hookBody;
|
|
202
|
+
|
|
203
|
+
// Check if it's an arrow function: () => { ... } or () => ...
|
|
204
|
+
if (hookBody.includes('=>')) {
|
|
205
|
+
const arrowMatch = hookBody.match(/=>\s*\{?([^}]*)\}?$/);
|
|
206
|
+
if (arrowMatch) {
|
|
207
|
+
callbackBody = arrowMatch[1];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if callback only uses stable global APIs or imported module-level constants
|
|
212
|
+
// Stable globals that don't need dependencies:
|
|
213
|
+
const stableGlobals = [
|
|
214
|
+
/window\.(location|document|localStorage|sessionStorage|navigator)/,
|
|
215
|
+
/document\.(documentElement|body|getElementById|querySelector)/,
|
|
216
|
+
/localStorage\.(getItem|setItem|removeItem|clear)/,
|
|
217
|
+
/sessionStorage\.(getItem|setItem|removeItem|clear)/,
|
|
218
|
+
/console\.(log|error|warn|info|debug)/,
|
|
219
|
+
/Math\./,
|
|
220
|
+
/Date\./,
|
|
221
|
+
/JSON\./,
|
|
222
|
+
/Object\./,
|
|
223
|
+
/Array\./,
|
|
224
|
+
/String\./,
|
|
225
|
+
/Number\./,
|
|
226
|
+
/Boolean\./,
|
|
227
|
+
/RegExp\./,
|
|
228
|
+
/Error\./,
|
|
229
|
+
/Promise\./,
|
|
230
|
+
/Symbol\./,
|
|
231
|
+
/Reflect\./,
|
|
232
|
+
/Proxy\./
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
// Check for imported module-level constants (classes, functions, constants)
|
|
236
|
+
// Pattern: ImportedClass.method() or importedFunction() or importedConstant
|
|
237
|
+
const importedModulePattern = /[A-Z]\w+\.\w+\(|^[A-Z]\w+\(|\b[A-Z][A-Z_]+/;
|
|
238
|
+
|
|
239
|
+
// Check if callback body only contains stable globals or simple operations
|
|
240
|
+
const hasOnlyStableGlobals = stableGlobals.some(pattern => pattern.test(callbackBody));
|
|
241
|
+
const hasImportedModule = importedModulePattern.test(callbackBody);
|
|
242
|
+
|
|
243
|
+
// Check if callback uses reactive values (props, state, context) that would need deps
|
|
244
|
+
const hasReactiveValues = /\b(props|state|context|use[A-Z]\w+\(\))/.test(callbackBody);
|
|
245
|
+
|
|
246
|
+
// Check for variable references that might be reactive
|
|
247
|
+
// Simple heuristic: if callback references variables that aren't stable globals
|
|
248
|
+
const variablePattern = /\b[a-z][a-zA-Z0-9_]*\b/g;
|
|
249
|
+
const variables = callbackBody.match(variablePattern) || [];
|
|
250
|
+
const hasNonGlobalVariables = variables.some(v => {
|
|
251
|
+
// Skip common stable globals and built-ins
|
|
252
|
+
const stableVars = ['window', 'document', 'localStorage', 'sessionStorage', 'console',
|
|
253
|
+
'Math', 'Date', 'JSON', 'Object', 'Array', 'String', 'Number',
|
|
254
|
+
'Boolean', 'RegExp', 'Error', 'Promise', 'Symbol', 'Reflect', 'Proxy',
|
|
255
|
+
'true', 'false', 'null', 'undefined', 'this', 'return', 'if', 'else',
|
|
256
|
+
'const', 'let', 'var', 'function', 'async', 'await', 'for', 'while',
|
|
257
|
+
'switch', 'case', 'default', 'break', 'continue', 'try', 'catch', 'finally'];
|
|
258
|
+
return !stableVars.includes(v) && !v.match(/^[A-Z]/); // Lowercase vars that aren't stable
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Only warn if callback uses reactive values or non-global variables
|
|
262
|
+
if (hasReactiveValues || (hasNonGlobalVariables && !hasOnlyStableGlobals && !hasImportedModule)) {
|
|
263
|
+
warnings.push({
|
|
264
|
+
type: 'missing-dependencies',
|
|
265
|
+
file: relativePath,
|
|
266
|
+
line: getLineNumber(content, depMatch.index),
|
|
267
|
+
message: `${hookName} has empty dependency array but may need dependencies`,
|
|
268
|
+
recommendation: 'Review the hook body and add all dependencies to the dependency array. If the callback only uses stable global APIs (window, document, localStorage, etc.) or imported module-level constants, an empty dependency array is correct.'
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Check hook order (hooks should be called in consistent order)
|
|
276
|
+
// This is a simplified check - full implementation would track hook order across renders
|
|
277
|
+
|
|
278
|
+
// SKIP hook grouping suggestions for pace-core library files
|
|
279
|
+
// Library components often have complex hook usage organized by feature/concern,
|
|
280
|
+
// and strict grouping is less critical than in consuming applications.
|
|
281
|
+
// The grouping suggestions are primarily for consuming apps, not library code.
|
|
282
|
+
if (isPaceCorePackage) {
|
|
283
|
+
continue; // Skip grouping suggestions for pace-core library files
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Filter to only actual React hooks (not custom hooks) for grouping suggestions
|
|
287
|
+
// Files with only custom hooks (like useToast()) don't need grouping suggestions
|
|
288
|
+
const actualReactHookCalls = hookCalls.filter(h => actualReactHooks.includes(h.name));
|
|
289
|
+
|
|
290
|
+
// Skip files that only have custom hooks (no actual React hooks)
|
|
291
|
+
// Custom hooks are typically called once and don't need grouping
|
|
292
|
+
// This also filters out files like Toast.tsx that only have forwardRef components
|
|
293
|
+
// (forwardRef components don't use React hooks, so actualReactHookCalls will be empty)
|
|
294
|
+
if (actualReactHookCalls.length === 0) {
|
|
295
|
+
continue; // No actual React hooks, skip grouping suggestion
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check if hooks are already grouped (look for grouping comments)
|
|
299
|
+
// Accept more flexible patterns:
|
|
300
|
+
// - Specific keywords: // ============================================================================ // REFS / STATE HOOKS / etc.
|
|
301
|
+
// - Section dividers: // ============================================================================ (any text)
|
|
302
|
+
// - Comment blocks near hooks: // HOOKS or // State management or similar
|
|
303
|
+
const hasGroupingComments =
|
|
304
|
+
/\/\/\s*=+\s*(REFS|STATE HOOKS|CUSTOM HOOKS|MEMOIZATION HOOKS|EFFECT HOOKS|HOOKS|STATE|EFFECTS|MEMOIZATION|CALLBACKS)/i.test(content) ||
|
|
305
|
+
/\/\/\s*=+\s*[A-Z].*HOOK/i.test(content) ||
|
|
306
|
+
/\/\/\s*=+\s*[A-Z].*STATE/i.test(content) ||
|
|
307
|
+
/\/\/\s*=+\s*[A-Z].*EFFECT/i.test(content);
|
|
308
|
+
|
|
309
|
+
// Only suggest grouping if:
|
|
310
|
+
// 1. File has actual React hooks (not just custom hooks)
|
|
311
|
+
// 2. Hooks are called multiple times (duplicate hook names)
|
|
312
|
+
// 3. Hooks are not already grouped (no grouping comments found)
|
|
313
|
+
const hookOrder = actualReactHookCalls.map(h => h.name);
|
|
314
|
+
const uniqueHooks = [...new Set(hookOrder)];
|
|
315
|
+
|
|
316
|
+
if (uniqueHooks.length !== hookOrder.length && !hasGroupingComments) {
|
|
317
|
+
// Hooks are called multiple times and not grouped
|
|
318
|
+
suggestions.push({
|
|
319
|
+
type: 'hook-order',
|
|
320
|
+
file: relativePath,
|
|
321
|
+
message: 'Consider grouping related hooks together for better readability',
|
|
322
|
+
recommendation: 'Group hooks by purpose (state hooks, effect hooks, custom hooks)'
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// Skip files with errors
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { issues, warnings, suggestions };
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
module.exports = hooksCheck;
|