@jmruthers/pace-core 0.6.6 → 0.6.7

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.
Files changed (246) hide show
  1. package/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
  2. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  3. package/audit-tool/audits/02-project-structure.cjs +255 -0
  4. package/audit-tool/audits/03-architecture.cjs +196 -0
  5. package/audit-tool/audits/04-code-quality.cjs +149 -0
  6. package/audit-tool/audits/05-styling.cjs +224 -0
  7. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  8. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  9. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  10. package/audit-tool/audits/09-operations.cjs +208 -0
  11. package/audit-tool/index.cjs +291 -0
  12. package/audit-tool/utils/code-utils.cjs +218 -0
  13. package/audit-tool/utils/file-utils.cjs +230 -0
  14. package/audit-tool/utils/report-utils.cjs +241 -0
  15. package/cursor-rules/00-standards-overview.mdc +156 -0
  16. package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
  17. package/cursor-rules/02-project-structure.mdc +37 -5
  18. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
  19. package/cursor-rules/04-code-quality.mdc +419 -0
  20. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
  21. package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
  22. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  23. package/cursor-rules/08-testing-documentation.mdc +324 -0
  24. package/cursor-rules/09-operations.mdc +365 -0
  25. package/dist/DataTable-7PMH7XN7.js +15 -0
  26. package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  27. package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
  28. package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
  29. package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
  30. package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
  31. package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
  32. package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
  33. package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
  34. package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
  35. package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
  36. package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
  37. package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
  38. package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
  39. package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
  40. package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
  41. package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
  42. package/dist/components.d.ts +5 -4
  43. package/dist/components.js +27 -32
  44. package/dist/eslint-rules/index.cjs +22 -9
  45. package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
  46. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  47. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  48. package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
  49. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  50. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  51. package/dist/hooks.d.ts +5 -5
  52. package/dist/hooks.js +6 -6
  53. package/dist/index.d.ts +6 -6
  54. package/dist/index.js +18 -17
  55. package/dist/rbac/index.js +6 -6
  56. package/dist/theming/runtime.d.ts +14 -1
  57. package/dist/theming/runtime.js +1 -1
  58. package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
  59. package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
  60. package/dist/utils.d.ts +2 -2
  61. package/dist/utils.js +8 -8
  62. package/docs/README.md +1 -1
  63. package/docs/api/modules.md +47 -31
  64. package/docs/api-reference/components.md +18 -20
  65. package/docs/api-reference/hooks.md +80 -80
  66. package/docs/api-reference/types.md +1 -1
  67. package/docs/api-reference/utilities.md +1 -1
  68. package/docs/architecture/README.md +1 -1
  69. package/docs/core-concepts/events.md +3 -3
  70. package/docs/core-concepts/organisations.md +6 -6
  71. package/docs/core-concepts/permissions.md +6 -6
  72. package/docs/documentation-index.md +12 -18
  73. package/docs/getting-started/documentation-index.md +1 -1
  74. package/docs/getting-started/examples/README.md +4 -4
  75. package/docs/getting-started/examples/full-featured-app.md +1 -1
  76. package/docs/getting-started/faq.md +2 -2
  77. package/docs/getting-started/quick-reference.md +4 -4
  78. package/docs/implementation-guides/authentication.md +15 -15
  79. package/docs/implementation-guides/component-styling.md +1 -1
  80. package/docs/implementation-guides/data-tables.md +126 -33
  81. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  82. package/docs/implementation-guides/dynamic-colors.md +3 -3
  83. package/docs/implementation-guides/file-upload-storage.md +2 -2
  84. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  85. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  86. package/docs/implementation-guides/large-datasets.md +3 -2
  87. package/docs/implementation-guides/organisation-security.md +2 -2
  88. package/docs/implementation-guides/performance.md +2 -2
  89. package/docs/implementation-guides/permission-enforcement.md +1 -1
  90. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  91. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  92. package/docs/rbac/README.md +5 -5
  93. package/docs/rbac/advanced-patterns.md +6 -6
  94. package/docs/rbac/api-reference.md +20 -20
  95. package/docs/rbac/event-based-apps.md +3 -3
  96. package/docs/rbac/examples.md +41 -41
  97. package/docs/rbac/getting-started.md +37 -37
  98. package/docs/rbac/performance.md +1 -1
  99. package/docs/rbac/quick-start.md +52 -52
  100. package/docs/rbac/secure-client-protection.md +1 -1
  101. package/docs/rbac/troubleshooting.md +1 -1
  102. package/docs/security/README.md +5 -5
  103. package/docs/standards/0-standards-overview.md +220 -0
  104. package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
  105. package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
  106. package/docs/standards/3-architecture-standards.md +606 -0
  107. package/docs/standards/4-code-quality-standards.md +728 -0
  108. package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
  109. package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
  110. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  111. package/docs/standards/8-testing-documentation-standards.md +401 -0
  112. package/docs/standards/9-operations-standards.md +1102 -0
  113. package/docs/standards/README.md +203 -104
  114. package/docs/troubleshooting/README.md +4 -4
  115. package/docs/troubleshooting/common-issues.md +2 -2
  116. package/docs/troubleshooting/debugging.md +9 -9
  117. package/docs/troubleshooting/migration.md +4 -4
  118. package/eslint-config-pace-core.cjs +21 -10
  119. package/package.json +6 -5
  120. package/scripts/install-cursor-rules.cjs +11 -243
  121. package/scripts/install-eslint-config.cjs +284 -0
  122. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
  123. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  124. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
  125. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  126. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  127. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  128. package/src/__tests__/templates/component.test.template.tsx +18 -15
  129. package/src/components/Calendar/Calendar.tsx +201 -47
  130. package/src/components/ContextSelector/ContextSelector.tsx +137 -153
  131. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  132. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  133. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  134. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  135. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  136. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  137. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  138. package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
  139. package/src/components/DataTable/components/EditableRow.tsx +5 -7
  140. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  141. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  142. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  143. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  144. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  145. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  146. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  147. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  148. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  149. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  150. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  151. package/src/components/DataTable/components/index.ts +2 -1
  152. package/src/components/DataTable/types.ts +0 -18
  153. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  154. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  155. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  156. package/src/components/DateTimeField/DateTimeField.tsx +7 -8
  157. package/src/components/Dialog/Dialog.test.tsx +1 -0
  158. package/src/components/Dialog/Dialog.tsx +25 -8
  159. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  160. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  161. package/src/components/FileUpload/FileUpload.tsx +112 -130
  162. package/src/components/Progress/Progress.tsx +2 -4
  163. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  164. package/src/components/Select/Select.tsx +86 -77
  165. package/src/components/Select/types.ts +3 -0
  166. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  167. package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
  168. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  169. package/src/hooks/public/usePublicEvent.ts +5 -5
  170. package/src/hooks/public/usePublicEventLogo.ts +5 -5
  171. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  172. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  173. package/src/hooks/useAppConfig.ts +2 -2
  174. package/src/hooks/useEventTheme.test.ts +7 -7
  175. package/src/hooks/useEventTheme.ts +1 -4
  176. package/src/hooks/useFileDisplay.ts +2 -2
  177. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  178. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  179. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  180. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  181. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  182. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  183. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  184. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  185. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
  186. package/src/styles/core.css +7 -0
  187. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  188. package/src/theming/parseEventColours.ts +22 -10
  189. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  190. package/src/utils/storage/README.md +1 -1
  191. package/cursor-rules/01-standards-compliance.mdc +0 -285
  192. package/cursor-rules/04-testing-standards.mdc +0 -270
  193. package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
  194. package/cursor-rules/06-code-quality.mdc +0 -311
  195. package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
  196. package/cursor-rules/10-error-handling-patterns.mdc +0 -179
  197. package/cursor-rules/11-performance-optimization.mdc +0 -169
  198. package/cursor-rules/12-ci-cd-integration.mdc +0 -150
  199. package/dist/DataTable-LRJL4IRV.js +0 -15
  200. package/dist/eslint-rules/rules/compliance.cjs +0 -348
  201. package/dist/eslint-rules/rules/components.cjs +0 -113
  202. package/dist/eslint-rules/rules/imports.cjs +0 -102
  203. package/docs/best-practices/README.md +0 -472
  204. package/docs/best-practices/accessibility.md +0 -604
  205. package/docs/best-practices/common-patterns.md +0 -516
  206. package/docs/best-practices/deployment.md +0 -1103
  207. package/docs/best-practices/performance.md +0 -1328
  208. package/docs/best-practices/security.md +0 -940
  209. package/docs/best-practices/testing.md +0 -1034
  210. package/docs/rbac/compliance/compliance-guide.md +0 -544
  211. package/docs/standards/01-standards-compliance.md +0 -188
  212. package/docs/standards/03-solid-principles.md +0 -39
  213. package/docs/standards/04-testing-standards.md +0 -36
  214. package/docs/standards/05-bug-reports-and-features.md +0 -27
  215. package/docs/standards/06-code-quality.md +0 -34
  216. package/docs/standards/07-tech-stack-compliance.md +0 -30
  217. package/docs/standards/10-error-handling-patterns.md +0 -401
  218. package/docs/standards/11-performance-optimization.md +0 -348
  219. package/docs/standards/12-ci-cd-integration.md +0 -370
  220. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
  221. package/scripts/audit/audit-compliance.cjs +0 -1295
  222. package/scripts/audit/audit-components.cjs +0 -260
  223. package/scripts/audit/audit-rbac.cjs +0 -954
  224. package/scripts/audit/audit-standards.cjs +0 -1268
  225. package/scripts/audit/index.cjs +0 -1927
  226. package/src/components/DataTable/components/DataTableBody.tsx +0 -478
  227. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  228. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  229. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  230. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  231. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  232. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  233. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  234. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  235. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  236. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  237. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  238. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  239. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  240. package/src/components/DataTable/utils/debugTools.ts +0 -514
  241. package/src/eslint-rules/index.cjs +0 -22
  242. package/src/eslint-rules/rules/components.cjs +0 -113
  243. package/src/eslint-rules/rules/imports.cjs +0 -102
  244. package/src/eslint-rules/rules/rbac.cjs +0 -790
  245. package/src/eslint-rules/utils/helpers.cjs +0 -42
  246. package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
@@ -1,12 +1,22 @@
1
1
  /**
2
- * General compliance rules
2
+ * pace-core Compliance Rules (Standard 1)
3
3
  * @package @jmruthers/pace-core
4
- * @module ESLintRules/rules/compliance
4
+ * @module ESLintRules/rules/pace-core-compliance
5
+ *
6
+ * Enforces pace-core usage patterns from Standard 1:
7
+ * - Use pace-core components instead of native HTML or custom implementations
8
+ * - Use pace-core hooks instead of custom hooks
9
+ * - Use pace-core utilities instead of custom utilities
10
+ * - Block restricted imports (libraries wrapped by pace-core)
11
+ * - Prefer pace-core Form component
12
+ * - Prevent component duplication
13
+ *
14
+ * Reference: packages/core/docs/standards/1-pace-core-compliance-standards.md
5
15
  */
6
16
 
7
17
  const path = require('path');
8
- const { getPaceCoreComponents, getPaceCoreHooks, getPaceCoreUtils } = require('../utils/manifest-loader.cjs');
9
- const { isPaceCoreSourceFile } = require('../utils/helpers.cjs');
18
+ const { getPaceCoreComponents, getPaceCoreHooks, getPaceCoreUtils, getRestrictedImports } = require('../utils/manifest-loader.cjs');
19
+ const { getPaceCoreAlternative, isPaceCoreSourceFile } = require('../utils/helpers.cjs');
10
20
 
11
21
  module.exports = {
12
22
  rules: {
@@ -19,7 +29,8 @@ module.exports = {
19
29
  docs: {
20
30
  description: 'Suggest using pace-core components instead of native HTML elements',
21
31
  category: 'Best Practices',
22
- recommended: true
32
+ recommended: true,
33
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
23
34
  },
24
35
  hasSuggestions: true,
25
36
  messages: {
@@ -94,7 +105,8 @@ module.exports = {
94
105
  docs: {
95
106
  description: 'Suggest using pace-core hooks instead of custom implementations',
96
107
  category: 'Best Practices',
97
- recommended: true
108
+ recommended: true,
109
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
98
110
  },
99
111
  messages: {
100
112
  preferPaceCoreHook: "Consider using '{{hook}}' from '@jmruthers/pace-core' instead of custom hook '{{customHook}}'"
@@ -155,7 +167,8 @@ module.exports = {
155
167
  docs: {
156
168
  description: 'Suggest using pace-core utilities instead of custom implementations',
157
169
  category: 'Best Practices',
158
- recommended: true
170
+ recommended: true,
171
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
159
172
  },
160
173
  messages: {
161
174
  preferPaceCoreUtil: "Consider using '{{util}}' from '@jmruthers/pace-core' instead of custom function '{{customUtil}}'"
@@ -243,7 +256,8 @@ module.exports = {
243
256
  docs: {
244
257
  description: 'Disallow local components with names matching pace-core components',
245
258
  category: 'Best Practices',
246
- recommended: true
259
+ recommended: true,
260
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
247
261
  },
248
262
  messages: {
249
263
  duplicateComponent: "Component '{{componentName}}' conflicts with pace-core component. Use '@jmruthers/pace-core' instead of creating a local version."
@@ -301,20 +315,23 @@ module.exports = {
301
315
  },
302
316
 
303
317
  /**
304
- * Disallow inline styles
318
+ * Block direct imports of libraries wrapped by pace-core
305
319
  */
306
- 'no-inline-styles': {
320
+ 'no-restricted-imports': {
307
321
  meta: {
308
322
  type: 'problem',
309
323
  docs: {
310
- description: 'Disallow inline styles. Use pace-core components or Tailwind classes instead.',
324
+ description: 'Disallow direct imports of libraries that pace-core wraps',
311
325
  category: 'Best Practices',
312
- recommended: true
326
+ recommended: true,
327
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
313
328
  },
329
+ fixable: 'code',
330
+ hasSuggestions: true,
314
331
  messages: {
315
- inlineStyle: 'Inline style detected. Use pace-core components or Tailwind classes instead.'
316
- },
317
- hasSuggestions: true
332
+ restrictedImport: '{{message}} Import from {{alternative}} instead.',
333
+ restrictedImportWithReason: '{{message}} {{reason}}'
334
+ }
318
335
  },
319
336
  create(context) {
320
337
  const filename = context.getFilename();
@@ -324,20 +341,165 @@ module.exports = {
324
341
  return {};
325
342
  }
326
343
 
344
+ const restrictedImports = getRestrictedImports();
345
+ const restrictedModules = restrictedImports.map(imp => imp.module);
346
+
347
+ // Also catch @radix-ui/* patterns
348
+ const radixPattern = /^@radix-ui\//;
349
+
327
350
  return {
328
- JSXAttribute(node) {
329
- if (node.name && node.name.name === 'style') {
351
+ ImportDeclaration(node) {
352
+ const importSource = node.source.value;
353
+
354
+ // Check exact matches
355
+ const restricted = restrictedImports.find(imp => imp.module === importSource);
356
+ if (restricted) {
330
357
  context.report({
331
- node,
332
- messageId: 'inlineStyle',
358
+ node: node.source,
359
+ messageId: 'restrictedImportWithReason',
360
+ data: {
361
+ message: `Direct import of '${importSource}' is not allowed.`,
362
+ reason: restricted.reason
363
+ },
333
364
  suggest: [{
334
- desc: 'Remove inline style and use pace-core component styling or Tailwind utility classes',
365
+ desc: `Use pace-core alternative: ${restricted.reason}`,
335
366
  fix(fixer) {
336
- // Remove the style attribute
337
- return fixer.remove(node);
367
+ // Suggest importing from pace-core instead
368
+ const paceCoreAlternative = getPaceCoreAlternative(importSource);
369
+ if (paceCoreAlternative) {
370
+ return fixer.replaceText(
371
+ node.source,
372
+ `'@jmruthers/pace-core${paceCoreAlternative}'`
373
+ );
374
+ }
375
+ return null;
338
376
  }
339
377
  }]
340
378
  });
379
+ return;
380
+ }
381
+
382
+ // Check @radix-ui/* pattern
383
+ if (radixPattern.test(importSource) && !restrictedModules.includes(importSource)) {
384
+ context.report({
385
+ node: node.source,
386
+ messageId: 'restrictedImport',
387
+ data: {
388
+ message: `Direct import of '${importSource}' is not allowed.`,
389
+ alternative: '@jmruthers/pace-core'
390
+ },
391
+ suggest: [{
392
+ desc: 'Use pace-core component instead',
393
+ fix(fixer) {
394
+ return fixer.replaceText(
395
+ node.source,
396
+ "'@jmruthers/pace-core'"
397
+ );
398
+ }
399
+ }]
400
+ });
401
+ }
402
+ }
403
+ };
404
+ }
405
+ },
406
+
407
+ /**
408
+ * Prefer pace-core Form component over plain form tags and direct react-hook-form usage
409
+ */
410
+ 'prefer-pace-core-form': {
411
+ meta: {
412
+ type: 'problem',
413
+ docs: {
414
+ description: 'Disallow plain <form> tags and direct react-hook-form imports. Use pace-core Form component instead.',
415
+ category: 'Best Practices',
416
+ recommended: true,
417
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/1-pace-core-compliance-standards.md'
418
+ },
419
+ messages: {
420
+ plainFormTag: "Plain <form> tag detected. Use pace-core Form component instead.",
421
+ reactHookFormImport: "Direct import from 'react-hook-form' detected: {{imports}}. Use pace-core Form component instead. useFormContext is allowed when Form is imported from pace-core."
422
+ },
423
+ hasSuggestions: true
424
+ },
425
+ create(context) {
426
+ const filename = context.getFilename();
427
+
428
+ // Exclude pace-core source files - these rules are for consuming apps
429
+ if (isPaceCoreSourceFile(filename)) {
430
+ return {};
431
+ }
432
+
433
+ let hasPaceCoreForm = false;
434
+ const problematicImports = ['useForm', 'FormProvider', 'Controller', 'useWatch', 'useFieldArray', 'useController'];
435
+
436
+ return {
437
+ ImportDeclaration(node) {
438
+ const importSource = node.source.value;
439
+
440
+ // Check if Form is imported from pace-core
441
+ if (importSource === '@jmruthers/pace-core' ||
442
+ importSource === '@jmruthers/pace-core/components') {
443
+ const hasForm = node.specifiers.some(spec => {
444
+ if (spec.type === 'ImportSpecifier') {
445
+ return spec.imported.name === 'Form';
446
+ }
447
+ return false;
448
+ });
449
+ if (hasForm) {
450
+ hasPaceCoreForm = true;
451
+ }
452
+ }
453
+
454
+ // Check for react-hook-form imports
455
+ if (importSource === 'react-hook-form') {
456
+ const importedItems = node.specifiers
457
+ .filter(spec => spec.type === 'ImportSpecifier')
458
+ .map(spec => spec.imported.name);
459
+
460
+ const foundProblematic = importedItems.filter(item => problematicImports.includes(item));
461
+
462
+ // Allow useFormContext when Form is imported from pace-core
463
+ const isAcceptable = hasPaceCoreForm &&
464
+ foundProblematic.length === 1 &&
465
+ foundProblematic[0] === 'useFormContext';
466
+
467
+ if (foundProblematic.length > 0 && !isAcceptable) {
468
+ context.report({
469
+ node: node.source,
470
+ messageId: 'reactHookFormImport',
471
+ data: {
472
+ imports: foundProblematic.join(', ')
473
+ },
474
+ suggest: [{
475
+ desc: 'Use pace-core Form component instead',
476
+ fix(fixer) {
477
+ return fixer.remove(node);
478
+ }
479
+ }]
480
+ });
481
+ }
482
+ }
483
+ },
484
+
485
+ JSXOpeningElement(node) {
486
+ // Check for plain <form> tags (lowercase, not Form component)
487
+ if (node.name.type === 'JSXIdentifier') {
488
+ const elementName = node.name.name;
489
+ // Check if it's lowercase 'form' (not 'Form' component)
490
+ if (elementName === 'form') {
491
+ context.report({
492
+ node,
493
+ messageId: 'plainFormTag',
494
+ suggest: [{
495
+ desc: 'Replace with pace-core Form component',
496
+ fix(fixer) {
497
+ // This is complex to auto-fix, so we'll just report
498
+ return null;
499
+ }
500
+ }]
501
+ });
502
+ }
341
503
  }
342
504
  }
343
505
  };
@@ -345,4 +507,3 @@ module.exports = {
345
507
  }
346
508
  }
347
509
  };
348
-
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Code Quality Rules (Standard 4)
3
+ * @package @jmruthers/pace-core
4
+ * @module ESLintRules/rules/code-quality
5
+ *
6
+ * Enforces code quality patterns from Standard 4:
7
+ * - Naming conventions (hooks: use*, providers: *Provider)
8
+ * - TypeScript best practices
9
+ *
10
+ * Reference: packages/core/docs/standards/4-code-quality-standards.md
11
+ */
12
+
13
+ const { isPaceCoreSourceFile } = require('../utils/helpers.cjs');
14
+
15
+ module.exports = {
16
+ rules: {
17
+ /**
18
+ * Enforce naming conventions for hooks and providers
19
+ */
20
+ 'naming-convention': {
21
+ meta: {
22
+ type: 'suggestion',
23
+ docs: {
24
+ description: 'Enforce naming conventions: hooks must start with "use", providers must end with "Provider"',
25
+ category: 'Best Practices',
26
+ recommended: true,
27
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/4-code-quality-standards.md'
28
+ },
29
+ messages: {
30
+ hookNaming: "Hook '{{name}}' must start with 'use' (e.g., useEventData, useUserProfile)",
31
+ providerNaming: "Provider '{{name}}' must end with 'Provider' (e.g., AuthProvider, OrganisationProvider)"
32
+ },
33
+ hasSuggestions: true
34
+ },
35
+ create(context) {
36
+ const filename = context.getFilename();
37
+
38
+ // Exclude pace-core source files - these rules are for consuming apps
39
+ if (isPaceCoreSourceFile(filename)) {
40
+ return {};
41
+ }
42
+
43
+ return {
44
+ FunctionDeclaration(node) {
45
+ if (!node.id) return;
46
+
47
+ const functionName = node.id.name;
48
+ if (!functionName) return;
49
+
50
+ // Check for hooks (functions that use React hooks)
51
+ const sourceCode = context.getSourceCode();
52
+ const functionText = sourceCode.getText(node.body);
53
+ const usesReactHooks = /use(State|Effect|Callback|Memo|Ref|Context|Reducer|LayoutEffect|ImperativeHandle|DebugValue)/.test(functionText);
54
+
55
+ // Check if function is a hook (starts with 'use' or uses React hooks)
56
+ if (usesReactHooks || functionName.startsWith('use')) {
57
+ if (!functionName.startsWith('use')) {
58
+ context.report({
59
+ node: node.id,
60
+ messageId: 'hookNaming',
61
+ data: { name: functionName },
62
+ suggest: [{
63
+ desc: `Rename to use${functionName.charAt(0).toUpperCase() + functionName.slice(1)}`,
64
+ fix(fixer) {
65
+ return null; // Complex rename
66
+ }
67
+ }]
68
+ });
69
+ }
70
+ }
71
+
72
+ // Check for providers (components that return JSX and have Provider in name or return Provider-like structure)
73
+ const returnsJSX = functionText.includes('return') && (functionText.includes('<') || functionText.includes('createElement'));
74
+ const hasProviderPattern = functionName.includes('Provider') ||
75
+ functionText.includes('Provider') ||
76
+ functionText.includes('Context.Provider');
77
+
78
+ if (returnsJSX && hasProviderPattern && !functionName.endsWith('Provider')) {
79
+ context.report({
80
+ node: node.id,
81
+ messageId: 'providerNaming',
82
+ data: { name: functionName },
83
+ suggest: [{
84
+ desc: `Rename to ${functionName}Provider`,
85
+ fix(fixer) {
86
+ return null; // Complex rename
87
+ }
88
+ }]
89
+ });
90
+ }
91
+ },
92
+
93
+ VariableDeclarator(node) {
94
+ if (!node.id || node.id.type !== 'Identifier') return;
95
+ if (!node.init) return;
96
+
97
+ const varName = node.id.name;
98
+ if (!varName) return;
99
+
100
+ // Check for hook variables (arrow functions that use React hooks)
101
+ if (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') {
102
+ const sourceCode = context.getSourceCode();
103
+ const functionText = sourceCode.getText(node.init);
104
+ const usesReactHooks = /use(State|Effect|Callback|Memo|Ref|Context|Reducer|LayoutEffect|ImperativeHandle|DebugValue)/.test(functionText);
105
+
106
+ if (usesReactHooks && !varName.startsWith('use')) {
107
+ context.report({
108
+ node: node.id,
109
+ messageId: 'hookNaming',
110
+ data: { name: varName },
111
+ suggest: [{
112
+ desc: `Rename to use${varName.charAt(0).toUpperCase() + varName.slice(1)}`,
113
+ fix(fixer) {
114
+ return null; // Complex rename
115
+ }
116
+ }]
117
+ });
118
+ }
119
+ }
120
+ }
121
+ };
122
+ }
123
+ },
124
+
125
+ /**
126
+ * Enforce component naming: PascalCase
127
+ */
128
+ 'component-naming': {
129
+ meta: {
130
+ type: 'suggestion',
131
+ docs: {
132
+ description: 'Enforce component naming: components must use PascalCase',
133
+ category: 'Best Practices',
134
+ recommended: true,
135
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/4-code-quality-standards.md'
136
+ },
137
+ messages: {
138
+ componentNaming: "Component '{{name}}' must use PascalCase (e.g., EventCard, UserProfile)"
139
+ },
140
+ hasSuggestions: true
141
+ },
142
+ create(context) {
143
+ const filename = context.getFilename();
144
+
145
+ // Exclude pace-core source files - these rules are for consuming apps
146
+ if (isPaceCoreSourceFile(filename)) {
147
+ return {};
148
+ }
149
+
150
+ const isComponentFile = filename.match(/(components|Components)\//);
151
+ if (!isComponentFile) {
152
+ return {};
153
+ }
154
+
155
+ return {
156
+ FunctionDeclaration(node) {
157
+ if (!node.id) return;
158
+
159
+ const functionName = node.id.name;
160
+ if (!functionName) return;
161
+
162
+ // Check if it's a component (returns JSX)
163
+ const sourceCode = context.getSourceCode();
164
+ const functionText = sourceCode.getText(node.body);
165
+ const returnsJSX = functionText.includes('return') && (functionText.includes('<') || functionText.includes('createElement'));
166
+
167
+ if (returnsJSX) {
168
+ // Check if it's PascalCase
169
+ const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(functionName);
170
+ if (!isPascalCase) {
171
+ context.report({
172
+ node: node.id,
173
+ messageId: 'componentNaming',
174
+ data: { name: functionName },
175
+ suggest: [{
176
+ desc: `Rename to ${functionName.charAt(0).toUpperCase() + functionName.slice(1).replace(/[^a-zA-Z0-9]/g, '')}`,
177
+ fix(fixer) {
178
+ return null; // Complex rename
179
+ }
180
+ }]
181
+ });
182
+ }
183
+ }
184
+ },
185
+
186
+ VariableDeclarator(node) {
187
+ if (!node.id || node.id.type !== 'Identifier') return;
188
+ if (!node.init) return;
189
+
190
+ const varName = node.id.name;
191
+ if (!varName) return;
192
+
193
+ // Check for component variables (arrow functions that return JSX)
194
+ if (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') {
195
+ const sourceCode = context.getSourceCode();
196
+ const functionText = sourceCode.getText(node.init);
197
+ const returnsJSX = functionText.includes('return') && (functionText.includes('<') || functionText.includes('createElement'));
198
+
199
+ if (returnsJSX) {
200
+ const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(varName);
201
+ if (!isPascalCase) {
202
+ context.report({
203
+ node: node.id,
204
+ messageId: 'componentNaming',
205
+ data: { name: varName },
206
+ suggest: [{
207
+ desc: `Rename to ${varName.charAt(0).toUpperCase() + varName.slice(1).replace(/[^a-zA-Z0-9]/g, '')}`,
208
+ fix(fixer) {
209
+ return null; // Complex rename
210
+ }
211
+ }]
212
+ });
213
+ }
214
+ }
215
+ }
216
+ }
217
+ };
218
+ }
219
+ },
220
+
221
+ /**
222
+ * Enforce type/interface naming: PascalCase
223
+ */
224
+ 'type-naming': {
225
+ meta: {
226
+ type: 'suggestion',
227
+ docs: {
228
+ description: 'Enforce type/interface naming: types and interfaces must use PascalCase',
229
+ category: 'Best Practices',
230
+ recommended: true,
231
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/4-code-quality-standards.md'
232
+ },
233
+ messages: {
234
+ typeNaming: "Type/interface '{{name}}' must use PascalCase (e.g., EventStatus, UserProfile)"
235
+ },
236
+ hasSuggestions: true
237
+ },
238
+ create(context) {
239
+ const filename = context.getFilename();
240
+
241
+ // Exclude pace-core source files - these rules are for consuming apps
242
+ if (isPaceCoreSourceFile(filename)) {
243
+ return {};
244
+ }
245
+
246
+ return {
247
+ TSInterfaceDeclaration(node) {
248
+ const interfaceName = node.id.name;
249
+ if (!interfaceName) return;
250
+
251
+ const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(interfaceName);
252
+ if (!isPascalCase) {
253
+ context.report({
254
+ node: node.id,
255
+ messageId: 'typeNaming',
256
+ data: { name: interfaceName },
257
+ suggest: [{
258
+ desc: `Rename to ${interfaceName.charAt(0).toUpperCase() + interfaceName.slice(1).replace(/[^a-zA-Z0-9]/g, '')}`,
259
+ fix(fixer) {
260
+ return null; // Complex rename
261
+ }
262
+ }]
263
+ });
264
+ }
265
+ },
266
+
267
+ TSTypeAliasDeclaration(node) {
268
+ const typeName = node.id.name;
269
+ if (!typeName) return;
270
+
271
+ const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(typeName);
272
+ if (!isPascalCase) {
273
+ context.report({
274
+ node: node.id,
275
+ messageId: 'typeNaming',
276
+ data: { name: typeName },
277
+ suggest: [{
278
+ desc: `Rename to ${typeName.charAt(0).toUpperCase() + typeName.slice(1).replace(/[^a-zA-Z0-9]/g, '')}`,
279
+ fix(fixer) {
280
+ return null; // Complex rename
281
+ }
282
+ }]
283
+ });
284
+ }
285
+ }
286
+ };
287
+ }
288
+ }
289
+ }
290
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Styling Rules (Standard 5)
3
+ * @package @jmruthers/pace-core
4
+ * @module ESLintRules/rules/styling
5
+ *
6
+ * Enforces styling patterns from Standard 5:
7
+ * - No inline styles (use pace-core components or Tailwind classes)
8
+ *
9
+ * Reference: packages/core/docs/standards/5-styling-standards.md
10
+ */
11
+
12
+ const { isPaceCoreSourceFile } = require('../utils/helpers.cjs');
13
+
14
+ module.exports = {
15
+ rules: {
16
+ /**
17
+ * Disallow inline styles
18
+ */
19
+ 'no-inline-styles': {
20
+ meta: {
21
+ type: 'problem',
22
+ docs: {
23
+ description: 'Disallow inline styles. Use pace-core components or Tailwind classes instead.',
24
+ category: 'Best Practices',
25
+ recommended: true,
26
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/5-styling-standards.md'
27
+ },
28
+ messages: {
29
+ inlineStyle: 'Inline style detected. Use pace-core components or Tailwind classes instead.'
30
+ },
31
+ hasSuggestions: true
32
+ },
33
+ create(context) {
34
+ const filename = context.getFilename();
35
+
36
+ // Exclude pace-core source files - these rules are for consuming apps
37
+ if (isPaceCoreSourceFile(filename)) {
38
+ return {};
39
+ }
40
+
41
+ return {
42
+ JSXAttribute(node) {
43
+ if (node.name && node.name.name === 'style') {
44
+ context.report({
45
+ node,
46
+ messageId: 'inlineStyle',
47
+ suggest: [{
48
+ desc: 'Remove inline style and use pace-core component styling or Tailwind utility classes',
49
+ fix(fixer) {
50
+ // Remove the style attribute
51
+ return fixer.remove(node);
52
+ }
53
+ }]
54
+ });
55
+ }
56
+ }
57
+ };
58
+ }
59
+ }
60
+ }
61
+ };