driftdetect-detectors 0.1.0
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/dist/accessibility/alt-text.d.ts +63 -0
- package/dist/accessibility/alt-text.d.ts.map +1 -0
- package/dist/accessibility/alt-text.js +100 -0
- package/dist/accessibility/alt-text.js.map +1 -0
- package/dist/accessibility/aria-roles.d.ts +65 -0
- package/dist/accessibility/aria-roles.d.ts.map +1 -0
- package/dist/accessibility/aria-roles.js +87 -0
- package/dist/accessibility/aria-roles.js.map +1 -0
- package/dist/accessibility/focus-management.d.ts +62 -0
- package/dist/accessibility/focus-management.d.ts.map +1 -0
- package/dist/accessibility/focus-management.js +88 -0
- package/dist/accessibility/focus-management.js.map +1 -0
- package/dist/accessibility/heading-hierarchy.d.ts +66 -0
- package/dist/accessibility/heading-hierarchy.d.ts.map +1 -0
- package/dist/accessibility/heading-hierarchy.js +94 -0
- package/dist/accessibility/heading-hierarchy.js.map +1 -0
- package/dist/accessibility/index.d.ts +25 -0
- package/dist/accessibility/index.d.ts.map +1 -0
- package/dist/accessibility/index.js +21 -0
- package/dist/accessibility/index.js.map +1 -0
- package/dist/accessibility/keyboard-nav.d.ts +63 -0
- package/dist/accessibility/keyboard-nav.d.ts.map +1 -0
- package/dist/accessibility/keyboard-nav.js +86 -0
- package/dist/accessibility/keyboard-nav.js.map +1 -0
- package/dist/accessibility/semantic-html.d.ts +76 -0
- package/dist/accessibility/semantic-html.d.ts.map +1 -0
- package/dist/accessibility/semantic-html.js +204 -0
- package/dist/accessibility/semantic-html.js.map +1 -0
- package/dist/api/client-patterns.d.ts +121 -0
- package/dist/api/client-patterns.d.ts.map +1 -0
- package/dist/api/client-patterns.js +478 -0
- package/dist/api/client-patterns.js.map +1 -0
- package/dist/api/error-format.d.ts +140 -0
- package/dist/api/error-format.d.ts.map +1 -0
- package/dist/api/error-format.js +614 -0
- package/dist/api/error-format.js.map +1 -0
- package/dist/api/http-methods.d.ts +255 -0
- package/dist/api/http-methods.d.ts.map +1 -0
- package/dist/api/http-methods.js +890 -0
- package/dist/api/http-methods.js.map +1 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +37 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/pagination.d.ts +133 -0
- package/dist/api/pagination.d.ts.map +1 -0
- package/dist/api/pagination.js +521 -0
- package/dist/api/pagination.js.map +1 -0
- package/dist/api/response-envelope.d.ts +261 -0
- package/dist/api/response-envelope.d.ts.map +1 -0
- package/dist/api/response-envelope.js +1050 -0
- package/dist/api/response-envelope.js.map +1 -0
- package/dist/api/retry-patterns.d.ts +117 -0
- package/dist/api/retry-patterns.d.ts.map +1 -0
- package/dist/api/retry-patterns.js +480 -0
- package/dist/api/retry-patterns.js.map +1 -0
- package/dist/api/route-structure.d.ts +128 -0
- package/dist/api/route-structure.d.ts.map +1 -0
- package/dist/api/route-structure.js +738 -0
- package/dist/api/route-structure.js.map +1 -0
- package/dist/auth/audit-logging.d.ts +80 -0
- package/dist/auth/audit-logging.d.ts.map +1 -0
- package/dist/auth/audit-logging.js +370 -0
- package/dist/auth/audit-logging.js.map +1 -0
- package/dist/auth/index.d.ts +33 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +49 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware-usage.d.ts +65 -0
- package/dist/auth/middleware-usage.d.ts.map +1 -0
- package/dist/auth/middleware-usage.js +192 -0
- package/dist/auth/middleware-usage.js.map +1 -0
- package/dist/auth/permission-checks.d.ts +60 -0
- package/dist/auth/permission-checks.d.ts.map +1 -0
- package/dist/auth/permission-checks.js +159 -0
- package/dist/auth/permission-checks.js.map +1 -0
- package/dist/auth/rbac-patterns.d.ts +68 -0
- package/dist/auth/rbac-patterns.d.ts.map +1 -0
- package/dist/auth/rbac-patterns.js +143 -0
- package/dist/auth/rbac-patterns.js.map +1 -0
- package/dist/auth/resource-ownership.d.ts +77 -0
- package/dist/auth/resource-ownership.d.ts.map +1 -0
- package/dist/auth/resource-ownership.js +324 -0
- package/dist/auth/resource-ownership.js.map +1 -0
- package/dist/auth/token-handling.d.ts +64 -0
- package/dist/auth/token-handling.d.ts.map +1 -0
- package/dist/auth/token-handling.js +151 -0
- package/dist/auth/token-handling.js.map +1 -0
- package/dist/base/ast-detector.d.ts +421 -0
- package/dist/base/ast-detector.d.ts.map +1 -0
- package/dist/base/ast-detector.js +699 -0
- package/dist/base/ast-detector.js.map +1 -0
- package/dist/base/base-detector.d.ts +366 -0
- package/dist/base/base-detector.d.ts.map +1 -0
- package/dist/base/base-detector.js +170 -0
- package/dist/base/base-detector.js.map +1 -0
- package/dist/base/index.d.ts +12 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +17 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/regex-detector.d.ts +421 -0
- package/dist/base/regex-detector.d.ts.map +1 -0
- package/dist/base/regex-detector.js +537 -0
- package/dist/base/regex-detector.js.map +1 -0
- package/dist/base/structural-detector.d.ts +424 -0
- package/dist/base/structural-detector.d.ts.map +1 -0
- package/dist/base/structural-detector.js +731 -0
- package/dist/base/structural-detector.js.map +1 -0
- package/dist/base/types.d.ts +53 -0
- package/dist/base/types.d.ts.map +1 -0
- package/dist/base/types.js +5 -0
- package/dist/base/types.js.map +1 -0
- package/dist/components/component-structure.d.ts +163 -0
- package/dist/components/component-structure.d.ts.map +1 -0
- package/dist/components/component-structure.js +500 -0
- package/dist/components/component-structure.js.map +1 -0
- package/dist/components/composition.d.ts +287 -0
- package/dist/components/composition.d.ts.map +1 -0
- package/dist/components/composition.js +1123 -0
- package/dist/components/composition.js.map +1 -0
- package/dist/components/duplicate-detection.d.ts +251 -0
- package/dist/components/duplicate-detection.d.ts.map +1 -0
- package/dist/components/duplicate-detection.js +804 -0
- package/dist/components/duplicate-detection.js.map +1 -0
- package/dist/components/index.d.ts +16 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +51 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/near-duplicate.d.ts +402 -0
- package/dist/components/near-duplicate.d.ts.map +1 -0
- package/dist/components/near-duplicate.js +1090 -0
- package/dist/components/near-duplicate.js.map +1 -0
- package/dist/components/props-patterns.d.ts +194 -0
- package/dist/components/props-patterns.d.ts.map +1 -0
- package/dist/components/props-patterns.js +795 -0
- package/dist/components/props-patterns.js.map +1 -0
- package/dist/components/ref-forwarding.d.ts +250 -0
- package/dist/components/ref-forwarding.d.ts.map +1 -0
- package/dist/components/ref-forwarding.js +832 -0
- package/dist/components/ref-forwarding.js.map +1 -0
- package/dist/components/state-patterns.d.ts +291 -0
- package/dist/components/state-patterns.d.ts.map +1 -0
- package/dist/components/state-patterns.js +970 -0
- package/dist/components/state-patterns.js.map +1 -0
- package/dist/config/config-validation.d.ts +74 -0
- package/dist/config/config-validation.d.ts.map +1 -0
- package/dist/config/config-validation.js +446 -0
- package/dist/config/config-validation.js.map +1 -0
- package/dist/config/default-values.d.ts +72 -0
- package/dist/config/default-values.d.ts.map +1 -0
- package/dist/config/default-values.js +386 -0
- package/dist/config/default-values.js.map +1 -0
- package/dist/config/env-naming.d.ts +73 -0
- package/dist/config/env-naming.d.ts.map +1 -0
- package/dist/config/env-naming.js +429 -0
- package/dist/config/env-naming.js.map +1 -0
- package/dist/config/environment-detection.d.ts +72 -0
- package/dist/config/environment-detection.d.ts.map +1 -0
- package/dist/config/environment-detection.js +400 -0
- package/dist/config/environment-detection.js.map +1 -0
- package/dist/config/feature-flags.d.ts +72 -0
- package/dist/config/feature-flags.d.ts.map +1 -0
- package/dist/config/feature-flags.js +384 -0
- package/dist/config/feature-flags.js.map +1 -0
- package/dist/config/index.d.ts +27 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +43 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/required-optional.d.ts +71 -0
- package/dist/config/required-optional.d.ts.map +1 -0
- package/dist/config/required-optional.js +344 -0
- package/dist/config/required-optional.js.map +1 -0
- package/dist/data-access/connection-pooling.d.ts +63 -0
- package/dist/data-access/connection-pooling.d.ts.map +1 -0
- package/dist/data-access/connection-pooling.js +297 -0
- package/dist/data-access/connection-pooling.js.map +1 -0
- package/dist/data-access/dto-patterns.d.ts +64 -0
- package/dist/data-access/dto-patterns.d.ts.map +1 -0
- package/dist/data-access/dto-patterns.js +291 -0
- package/dist/data-access/dto-patterns.js.map +1 -0
- package/dist/data-access/index.d.ts +31 -0
- package/dist/data-access/index.d.ts.map +1 -0
- package/dist/data-access/index.js +49 -0
- package/dist/data-access/index.js.map +1 -0
- package/dist/data-access/n-plus-one.d.ts +60 -0
- package/dist/data-access/n-plus-one.d.ts.map +1 -0
- package/dist/data-access/n-plus-one.js +264 -0
- package/dist/data-access/n-plus-one.js.map +1 -0
- package/dist/data-access/query-patterns.d.ts +64 -0
- package/dist/data-access/query-patterns.d.ts.map +1 -0
- package/dist/data-access/query-patterns.js +314 -0
- package/dist/data-access/query-patterns.js.map +1 -0
- package/dist/data-access/repository-pattern.d.ts +62 -0
- package/dist/data-access/repository-pattern.d.ts.map +1 -0
- package/dist/data-access/repository-pattern.js +257 -0
- package/dist/data-access/repository-pattern.js.map +1 -0
- package/dist/data-access/transaction-patterns.d.ts +61 -0
- package/dist/data-access/transaction-patterns.d.ts.map +1 -0
- package/dist/data-access/transaction-patterns.js +277 -0
- package/dist/data-access/transaction-patterns.js.map +1 -0
- package/dist/data-access/validation-patterns.d.ts +62 -0
- package/dist/data-access/validation-patterns.d.ts.map +1 -0
- package/dist/data-access/validation-patterns.js +301 -0
- package/dist/data-access/validation-patterns.js.map +1 -0
- package/dist/documentation/deprecation.d.ts +62 -0
- package/dist/documentation/deprecation.d.ts.map +1 -0
- package/dist/documentation/deprecation.js +83 -0
- package/dist/documentation/deprecation.js.map +1 -0
- package/dist/documentation/example-code.d.ts +64 -0
- package/dist/documentation/example-code.d.ts.map +1 -0
- package/dist/documentation/example-code.js +79 -0
- package/dist/documentation/example-code.js.map +1 -0
- package/dist/documentation/index.d.ts +22 -0
- package/dist/documentation/index.d.ts.map +1 -0
- package/dist/documentation/index.js +19 -0
- package/dist/documentation/index.js.map +1 -0
- package/dist/documentation/jsdoc-patterns.d.ts +72 -0
- package/dist/documentation/jsdoc-patterns.d.ts.map +1 -0
- package/dist/documentation/jsdoc-patterns.js +92 -0
- package/dist/documentation/jsdoc-patterns.js.map +1 -0
- package/dist/documentation/readme-structure.d.ts +67 -0
- package/dist/documentation/readme-structure.d.ts.map +1 -0
- package/dist/documentation/readme-structure.js +76 -0
- package/dist/documentation/readme-structure.js.map +1 -0
- package/dist/documentation/todo-patterns.d.ts +67 -0
- package/dist/documentation/todo-patterns.d.ts.map +1 -0
- package/dist/documentation/todo-patterns.js +73 -0
- package/dist/documentation/todo-patterns.js.map +1 -0
- package/dist/errors/async-errors.d.ts +72 -0
- package/dist/errors/async-errors.d.ts.map +1 -0
- package/dist/errors/async-errors.js +214 -0
- package/dist/errors/async-errors.js.map +1 -0
- package/dist/errors/circuit-breaker.d.ts +53 -0
- package/dist/errors/circuit-breaker.d.ts.map +1 -0
- package/dist/errors/circuit-breaker.js +241 -0
- package/dist/errors/circuit-breaker.js.map +1 -0
- package/dist/errors/error-codes.d.ts +73 -0
- package/dist/errors/error-codes.d.ts.map +1 -0
- package/dist/errors/error-codes.js +211 -0
- package/dist/errors/error-codes.js.map +1 -0
- package/dist/errors/error-logging.d.ts +73 -0
- package/dist/errors/error-logging.d.ts.map +1 -0
- package/dist/errors/error-logging.js +256 -0
- package/dist/errors/error-logging.js.map +1 -0
- package/dist/errors/error-propagation.d.ts +73 -0
- package/dist/errors/error-propagation.d.ts.map +1 -0
- package/dist/errors/error-propagation.js +244 -0
- package/dist/errors/error-propagation.js.map +1 -0
- package/dist/errors/exception-hierarchy.d.ts +75 -0
- package/dist/errors/exception-hierarchy.d.ts.map +1 -0
- package/dist/errors/exception-hierarchy.js +259 -0
- package/dist/errors/exception-hierarchy.js.map +1 -0
- package/dist/errors/index.d.ts +31 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +49 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/try-catch-placement.d.ts +73 -0
- package/dist/errors/try-catch-placement.d.ts.map +1 -0
- package/dist/errors/try-catch-placement.js +214 -0
- package/dist/errors/try-catch-placement.js.map +1 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +245 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/context-fields.d.ts +48 -0
- package/dist/logging/context-fields.d.ts.map +1 -0
- package/dist/logging/context-fields.js +160 -0
- package/dist/logging/context-fields.js.map +1 -0
- package/dist/logging/correlation-ids.d.ts +44 -0
- package/dist/logging/correlation-ids.d.ts.map +1 -0
- package/dist/logging/correlation-ids.js +144 -0
- package/dist/logging/correlation-ids.js.map +1 -0
- package/dist/logging/health-checks.d.ts +45 -0
- package/dist/logging/health-checks.d.ts.map +1 -0
- package/dist/logging/health-checks.js +165 -0
- package/dist/logging/health-checks.js.map +1 -0
- package/dist/logging/index.d.ts +31 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +49 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/log-levels.d.ts +46 -0
- package/dist/logging/log-levels.d.ts.map +1 -0
- package/dist/logging/log-levels.js +178 -0
- package/dist/logging/log-levels.js.map +1 -0
- package/dist/logging/metric-naming.d.ts +46 -0
- package/dist/logging/metric-naming.d.ts.map +1 -0
- package/dist/logging/metric-naming.js +157 -0
- package/dist/logging/metric-naming.js.map +1 -0
- package/dist/logging/pii-redaction.d.ts +44 -0
- package/dist/logging/pii-redaction.d.ts.map +1 -0
- package/dist/logging/pii-redaction.js +166 -0
- package/dist/logging/pii-redaction.js.map +1 -0
- package/dist/logging/structured-format.d.ts +53 -0
- package/dist/logging/structured-format.d.ts.map +1 -0
- package/dist/logging/structured-format.js +235 -0
- package/dist/logging/structured-format.js.map +1 -0
- package/dist/performance/bundle-size.d.ts +79 -0
- package/dist/performance/bundle-size.d.ts.map +1 -0
- package/dist/performance/bundle-size.js +276 -0
- package/dist/performance/bundle-size.js.map +1 -0
- package/dist/performance/caching-patterns.d.ts +78 -0
- package/dist/performance/caching-patterns.d.ts.map +1 -0
- package/dist/performance/caching-patterns.js +257 -0
- package/dist/performance/caching-patterns.js.map +1 -0
- package/dist/performance/code-splitting.d.ts +86 -0
- package/dist/performance/code-splitting.d.ts.map +1 -0
- package/dist/performance/code-splitting.js +447 -0
- package/dist/performance/code-splitting.js.map +1 -0
- package/dist/performance/debounce-throttle.d.ts +75 -0
- package/dist/performance/debounce-throttle.d.ts.map +1 -0
- package/dist/performance/debounce-throttle.js +232 -0
- package/dist/performance/debounce-throttle.js.map +1 -0
- package/dist/performance/index.d.ts +28 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +39 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/lazy-loading.d.ts +75 -0
- package/dist/performance/lazy-loading.d.ts.map +1 -0
- package/dist/performance/lazy-loading.js +233 -0
- package/dist/performance/lazy-loading.js.map +1 -0
- package/dist/performance/memoization.d.ts +75 -0
- package/dist/performance/memoization.d.ts.map +1 -0
- package/dist/performance/memoization.js +251 -0
- package/dist/performance/memoization.js.map +1 -0
- package/dist/registry/detector-registry.d.ts +266 -0
- package/dist/registry/detector-registry.d.ts.map +1 -0
- package/dist/registry/detector-registry.js +526 -0
- package/dist/registry/detector-registry.js.map +1 -0
- package/dist/registry/index.d.ts +10 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +10 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/loader.d.ts +232 -0
- package/dist/registry/loader.d.ts.map +1 -0
- package/dist/registry/loader.js +419 -0
- package/dist/registry/loader.js.map +1 -0
- package/dist/registry/types.d.ts +111 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +19 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/security/csp-headers.d.ts +78 -0
- package/dist/security/csp-headers.d.ts.map +1 -0
- package/dist/security/csp-headers.js +401 -0
- package/dist/security/csp-headers.js.map +1 -0
- package/dist/security/csrf-protection.d.ts +72 -0
- package/dist/security/csrf-protection.d.ts.map +1 -0
- package/dist/security/csrf-protection.js +344 -0
- package/dist/security/csrf-protection.js.map +1 -0
- package/dist/security/index.d.ts +30 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +48 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/input-sanitization.d.ts +74 -0
- package/dist/security/input-sanitization.d.ts.map +1 -0
- package/dist/security/input-sanitization.js +373 -0
- package/dist/security/input-sanitization.js.map +1 -0
- package/dist/security/rate-limiting.d.ts +81 -0
- package/dist/security/rate-limiting.d.ts.map +1 -0
- package/dist/security/rate-limiting.js +535 -0
- package/dist/security/rate-limiting.js.map +1 -0
- package/dist/security/secret-management.d.ts +83 -0
- package/dist/security/secret-management.d.ts.map +1 -0
- package/dist/security/secret-management.js +547 -0
- package/dist/security/secret-management.js.map +1 -0
- package/dist/security/sql-injection.d.ts +76 -0
- package/dist/security/sql-injection.d.ts.map +1 -0
- package/dist/security/sql-injection.js +383 -0
- package/dist/security/sql-injection.js.map +1 -0
- package/dist/security/xss-prevention.d.ts +80 -0
- package/dist/security/xss-prevention.d.ts.map +1 -0
- package/dist/security/xss-prevention.js +416 -0
- package/dist/security/xss-prevention.js.map +1 -0
- package/dist/structural/barrel-exports.d.ts +178 -0
- package/dist/structural/barrel-exports.d.ts.map +1 -0
- package/dist/structural/barrel-exports.js +553 -0
- package/dist/structural/barrel-exports.js.map +1 -0
- package/dist/structural/circular-deps.d.ts +140 -0
- package/dist/structural/circular-deps.d.ts.map +1 -0
- package/dist/structural/circular-deps.js +422 -0
- package/dist/structural/circular-deps.js.map +1 -0
- package/dist/structural/co-location.d.ts +202 -0
- package/dist/structural/co-location.d.ts.map +1 -0
- package/dist/structural/co-location.js +640 -0
- package/dist/structural/co-location.js.map +1 -0
- package/dist/structural/directory-structure.d.ts +151 -0
- package/dist/structural/directory-structure.d.ts.map +1 -0
- package/dist/structural/directory-structure.js +457 -0
- package/dist/structural/directory-structure.js.map +1 -0
- package/dist/structural/file-naming.d.ts +61 -0
- package/dist/structural/file-naming.d.ts.map +1 -0
- package/dist/structural/file-naming.js +231 -0
- package/dist/structural/file-naming.js.map +1 -0
- package/dist/structural/import-ordering.d.ts +212 -0
- package/dist/structural/import-ordering.d.ts.map +1 -0
- package/dist/structural/import-ordering.js +821 -0
- package/dist/structural/import-ordering.js.map +1 -0
- package/dist/structural/index.d.ts +23 -0
- package/dist/structural/index.d.ts.map +1 -0
- package/dist/structural/index.js +26 -0
- package/dist/structural/index.js.map +1 -0
- package/dist/structural/module-boundaries.d.ts +164 -0
- package/dist/structural/module-boundaries.d.ts.map +1 -0
- package/dist/structural/module-boundaries.js +616 -0
- package/dist/structural/module-boundaries.js.map +1 -0
- package/dist/structural/package-boundaries.d.ts +182 -0
- package/dist/structural/package-boundaries.d.ts.map +1 -0
- package/dist/structural/package-boundaries.js +602 -0
- package/dist/structural/package-boundaries.js.map +1 -0
- package/dist/styling/class-naming.d.ts +263 -0
- package/dist/styling/class-naming.d.ts.map +1 -0
- package/dist/styling/class-naming.js +892 -0
- package/dist/styling/class-naming.js.map +1 -0
- package/dist/styling/color-usage.d.ts +213 -0
- package/dist/styling/color-usage.d.ts.map +1 -0
- package/dist/styling/color-usage.js +732 -0
- package/dist/styling/color-usage.js.map +1 -0
- package/dist/styling/design-tokens.d.ts +212 -0
- package/dist/styling/design-tokens.d.ts.map +1 -0
- package/dist/styling/design-tokens.js +748 -0
- package/dist/styling/design-tokens.js.map +1 -0
- package/dist/styling/index.d.ts +16 -0
- package/dist/styling/index.d.ts.map +1 -0
- package/dist/styling/index.js +56 -0
- package/dist/styling/index.js.map +1 -0
- package/dist/styling/responsive.d.ts +304 -0
- package/dist/styling/responsive.d.ts.map +1 -0
- package/dist/styling/responsive.js +888 -0
- package/dist/styling/responsive.js.map +1 -0
- package/dist/styling/spacing-scale.d.ts +248 -0
- package/dist/styling/spacing-scale.d.ts.map +1 -0
- package/dist/styling/spacing-scale.js +865 -0
- package/dist/styling/spacing-scale.js.map +1 -0
- package/dist/styling/tailwind-patterns.d.ts +305 -0
- package/dist/styling/tailwind-patterns.d.ts.map +1 -0
- package/dist/styling/tailwind-patterns.js +1181 -0
- package/dist/styling/tailwind-patterns.js.map +1 -0
- package/dist/styling/typography.d.ts +281 -0
- package/dist/styling/typography.d.ts.map +1 -0
- package/dist/styling/typography.js +1004 -0
- package/dist/styling/typography.js.map +1 -0
- package/dist/styling/z-index-scale.d.ts +270 -0
- package/dist/styling/z-index-scale.d.ts.map +1 -0
- package/dist/styling/z-index-scale.js +714 -0
- package/dist/styling/z-index-scale.js.map +1 -0
- package/dist/testing/co-location.d.ts +42 -0
- package/dist/testing/co-location.d.ts.map +1 -0
- package/dist/testing/co-location.js +134 -0
- package/dist/testing/co-location.js.map +1 -0
- package/dist/testing/describe-naming.d.ts +47 -0
- package/dist/testing/describe-naming.d.ts.map +1 -0
- package/dist/testing/describe-naming.js +150 -0
- package/dist/testing/describe-naming.js.map +1 -0
- package/dist/testing/file-naming.d.ts +44 -0
- package/dist/testing/file-naming.d.ts.map +1 -0
- package/dist/testing/file-naming.js +131 -0
- package/dist/testing/file-naming.js.map +1 -0
- package/dist/testing/fixture-patterns.d.ts +52 -0
- package/dist/testing/fixture-patterns.d.ts.map +1 -0
- package/dist/testing/fixture-patterns.js +228 -0
- package/dist/testing/fixture-patterns.js.map +1 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +49 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-patterns.d.ts +53 -0
- package/dist/testing/mock-patterns.d.ts.map +1 -0
- package/dist/testing/mock-patterns.js +264 -0
- package/dist/testing/mock-patterns.js.map +1 -0
- package/dist/testing/setup-teardown.d.ts +55 -0
- package/dist/testing/setup-teardown.d.ts.map +1 -0
- package/dist/testing/setup-teardown.js +262 -0
- package/dist/testing/setup-teardown.js.map +1 -0
- package/dist/testing/test-structure.d.ts +51 -0
- package/dist/testing/test-structure.d.ts.map +1 -0
- package/dist/testing/test-structure.js +225 -0
- package/dist/testing/test-structure.js.map +1 -0
- package/dist/types/any-usage.d.ts +99 -0
- package/dist/types/any-usage.d.ts.map +1 -0
- package/dist/types/any-usage.js +641 -0
- package/dist/types/any-usage.js.map +1 -0
- package/dist/types/file-location.d.ts +76 -0
- package/dist/types/file-location.d.ts.map +1 -0
- package/dist/types/file-location.js +395 -0
- package/dist/types/file-location.js.map +1 -0
- package/dist/types/generic-patterns.d.ts +97 -0
- package/dist/types/generic-patterns.d.ts.map +1 -0
- package/dist/types/generic-patterns.js +615 -0
- package/dist/types/generic-patterns.js.map +1 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +43 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interface-vs-type.d.ts +81 -0
- package/dist/types/interface-vs-type.d.ts.map +1 -0
- package/dist/types/interface-vs-type.js +440 -0
- package/dist/types/interface-vs-type.js.map +1 -0
- package/dist/types/naming-conventions.d.ts +84 -0
- package/dist/types/naming-conventions.d.ts.map +1 -0
- package/dist/types/naming-conventions.js +455 -0
- package/dist/types/naming-conventions.js.map +1 -0
- package/dist/types/type-assertions.d.ts +98 -0
- package/dist/types/type-assertions.d.ts.map +1 -0
- package/dist/types/type-assertions.js +639 -0
- package/dist/types/type-assertions.js.map +1 -0
- package/dist/types/utility-types.d.ts +110 -0
- package/dist/types/utility-types.d.ts.map +1 -0
- package/dist/types/utility-types.js +547 -0
- package/dist/types/utility-types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition Detector - Component composition pattern detection
|
|
3
|
+
*
|
|
4
|
+
* Detects composition patterns including children prop usage, render props,
|
|
5
|
+
* Higher-Order Components (HOCs), compound components, slot-based composition,
|
|
6
|
+
* Provider/Consumer patterns, and controlled vs uncontrolled components.
|
|
7
|
+
*
|
|
8
|
+
* @requirements 8.6 - THE Component_Detector SHALL detect component composition patterns
|
|
9
|
+
*/
|
|
10
|
+
import { ASTDetector } from '../base/index.js';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Default configuration for composition pattern detection
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_COMPOSITION_CONFIG = {
|
|
18
|
+
maxHOCDepth: 3,
|
|
19
|
+
maxRenderProps: 3,
|
|
20
|
+
maxContextNesting: 4,
|
|
21
|
+
detectControlledPatterns: true,
|
|
22
|
+
flagPropDrilling: true,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Common HOC patterns
|
|
26
|
+
*/
|
|
27
|
+
export const HOC_PATTERNS = [
|
|
28
|
+
'withRouter',
|
|
29
|
+
'withStyles',
|
|
30
|
+
'withTheme',
|
|
31
|
+
'withAuth',
|
|
32
|
+
'withLoading',
|
|
33
|
+
'withErrorBoundary',
|
|
34
|
+
'connect',
|
|
35
|
+
'memo',
|
|
36
|
+
'forwardRef',
|
|
37
|
+
'observer',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Common render prop names
|
|
41
|
+
*/
|
|
42
|
+
export const RENDER_PROP_NAMES = [
|
|
43
|
+
'render',
|
|
44
|
+
'children',
|
|
45
|
+
'renderItem',
|
|
46
|
+
'renderHeader',
|
|
47
|
+
'renderFooter',
|
|
48
|
+
'renderEmpty',
|
|
49
|
+
'renderLoading',
|
|
50
|
+
'renderError',
|
|
51
|
+
'component',
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Controlled component prop patterns
|
|
55
|
+
*/
|
|
56
|
+
export const CONTROLLED_PROP_PATTERNS = [
|
|
57
|
+
{ value: 'value', onChange: 'onChange' },
|
|
58
|
+
{ value: 'checked', onChange: 'onChange' },
|
|
59
|
+
{ value: 'selected', onChange: 'onSelect' },
|
|
60
|
+
{ value: 'open', onChange: 'onOpenChange' },
|
|
61
|
+
{ value: 'visible', onChange: 'onVisibleChange' },
|
|
62
|
+
{ value: 'expanded', onChange: 'onExpandedChange' },
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Uncontrolled component prop patterns
|
|
66
|
+
*/
|
|
67
|
+
export const UNCONTROLLED_PROP_PATTERNS = [
|
|
68
|
+
'defaultValue',
|
|
69
|
+
'defaultChecked',
|
|
70
|
+
'defaultSelected',
|
|
71
|
+
'defaultOpen',
|
|
72
|
+
'defaultVisible',
|
|
73
|
+
'defaultExpanded',
|
|
74
|
+
];
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Helper Functions - Component Detection
|
|
77
|
+
// ============================================================================
|
|
78
|
+
/**
|
|
79
|
+
* Check if a node represents a React component
|
|
80
|
+
*/
|
|
81
|
+
export function isReactComponent(node, content) {
|
|
82
|
+
if (node.type === 'function_declaration' ||
|
|
83
|
+
node.type === 'arrow_function' ||
|
|
84
|
+
node.type === 'function_expression') {
|
|
85
|
+
const name = getComponentName(node, content);
|
|
86
|
+
if (!name || !/^[A-Z]/.test(name)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const nodeText = node.text;
|
|
90
|
+
return nodeText.includes('<') && (nodeText.includes('/>') || nodeText.includes('</'));
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the component name from a node
|
|
96
|
+
*/
|
|
97
|
+
export function getComponentName(node, content) {
|
|
98
|
+
if (node.type === 'function_declaration') {
|
|
99
|
+
const nameNode = node.children.find(c => c.type === 'identifier');
|
|
100
|
+
return nameNode?.text;
|
|
101
|
+
}
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
const line = lines[node.startPosition.row];
|
|
104
|
+
if (line) {
|
|
105
|
+
const match = line.match(/(?:const|let|var|export\s+(?:const|let|var)?)\s+([A-Z][a-zA-Z0-9]*)\s*[=:]/);
|
|
106
|
+
if (match && match[1]) {
|
|
107
|
+
return match[1];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Helper Functions - Children Prop Detection
|
|
114
|
+
// ============================================================================
|
|
115
|
+
/**
|
|
116
|
+
* Detect children prop usage in a component
|
|
117
|
+
*/
|
|
118
|
+
export function detectChildrenProp(nodeText) {
|
|
119
|
+
// Check for children in props destructuring
|
|
120
|
+
const hasChildrenDestructured = /\{\s*[^}]*\bchildren\b[^}]*\}/.test(nodeText);
|
|
121
|
+
// Check for props.children usage
|
|
122
|
+
const hasPropsChildren = /props\.children/.test(nodeText);
|
|
123
|
+
// Check for children being rendered
|
|
124
|
+
const rendersChildren = /\{children\}|\{props\.children\}/.test(nodeText);
|
|
125
|
+
// Check for React.Children usage
|
|
126
|
+
const usesReactChildren = /React\.Children\.|Children\./.test(nodeText);
|
|
127
|
+
if (hasChildrenDestructured || hasPropsChildren || rendersChildren || usesReactChildren) {
|
|
128
|
+
const line = nodeText.split('\n').findIndex(l => /children/.test(l)) + 1;
|
|
129
|
+
return {
|
|
130
|
+
pattern: 'children-prop',
|
|
131
|
+
componentName: '',
|
|
132
|
+
line,
|
|
133
|
+
column: 1,
|
|
134
|
+
details: {},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if component accepts children prop
|
|
141
|
+
*/
|
|
142
|
+
export function acceptsChildrenProp(nodeText) {
|
|
143
|
+
// Check for children in type definition
|
|
144
|
+
const hasChildrenType = /children\s*[?:]?\s*:?\s*(?:React\.)?(?:ReactNode|ReactElement|JSX\.Element)/.test(nodeText);
|
|
145
|
+
// Check for PropsWithChildren
|
|
146
|
+
const hasPropsWithChildren = /PropsWithChildren/.test(nodeText);
|
|
147
|
+
// Check for children in destructuring or usage
|
|
148
|
+
const hasChildrenUsage = /\bchildren\b/.test(nodeText);
|
|
149
|
+
return hasChildrenType || hasPropsWithChildren || hasChildrenUsage;
|
|
150
|
+
}
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Helper Functions - Render Props Detection
|
|
153
|
+
// ============================================================================
|
|
154
|
+
/**
|
|
155
|
+
* Detect render props pattern in a component
|
|
156
|
+
*/
|
|
157
|
+
export function detectRenderProps(nodeText) {
|
|
158
|
+
const renderPropNames = [];
|
|
159
|
+
// Check for render prop in props
|
|
160
|
+
for (const propName of RENDER_PROP_NAMES) {
|
|
161
|
+
// Match: render={...}, renderItem={...}, etc.
|
|
162
|
+
const propPattern = new RegExp(`\\b${propName}\\s*[=:]\\s*\\{?\\s*\\(?`, 'g');
|
|
163
|
+
if (propPattern.test(nodeText)) {
|
|
164
|
+
renderPropNames.push(propName);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Check for children as function pattern
|
|
168
|
+
const childrenAsFunction = /children\s*\(\s*[^)]*\s*\)/.test(nodeText) ||
|
|
169
|
+
/\{children\s*&&\s*children\s*\(/.test(nodeText) ||
|
|
170
|
+
/typeof\s+children\s*===\s*['"]function['"]/.test(nodeText);
|
|
171
|
+
if (childrenAsFunction && !renderPropNames.includes('children')) {
|
|
172
|
+
renderPropNames.push('children');
|
|
173
|
+
}
|
|
174
|
+
// Check for render prop being called
|
|
175
|
+
const renderPropCalls = nodeText.match(/\b(render\w*)\s*\(\s*[^)]*\s*\)/g);
|
|
176
|
+
if (renderPropCalls) {
|
|
177
|
+
for (const call of renderPropCalls) {
|
|
178
|
+
const match = call.match(/\b(render\w*)\s*\(/);
|
|
179
|
+
if (match && match[1] && !renderPropNames.includes(match[1])) {
|
|
180
|
+
renderPropNames.push(match[1]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (renderPropNames.length > 0) {
|
|
185
|
+
const line = nodeText.split('\n').findIndex(l => renderPropNames.some(name => l.includes(name))) + 1;
|
|
186
|
+
return {
|
|
187
|
+
pattern: 'render-props',
|
|
188
|
+
componentName: '',
|
|
189
|
+
line,
|
|
190
|
+
column: 1,
|
|
191
|
+
details: {
|
|
192
|
+
renderPropNames,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Helper Functions - HOC Detection
|
|
200
|
+
// ============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Detect Higher-Order Component pattern
|
|
203
|
+
*/
|
|
204
|
+
export function detectHOC(content) {
|
|
205
|
+
const results = [];
|
|
206
|
+
// Pattern 1: export default withX(withY(Component))
|
|
207
|
+
const exportHOCPattern = /export\s+default\s+((?:with\w+|connect|memo|observer|forwardRef)\s*\([^)]*\))+/g;
|
|
208
|
+
let match;
|
|
209
|
+
while ((match = exportHOCPattern.exec(content)) !== null) {
|
|
210
|
+
const hocChain = match[1];
|
|
211
|
+
if (!hocChain)
|
|
212
|
+
continue;
|
|
213
|
+
const hocNames = extractHOCNames(hocChain);
|
|
214
|
+
if (hocNames.length > 0) {
|
|
215
|
+
const beforeMatch = content.slice(0, match.index);
|
|
216
|
+
const line = beforeMatch.split('\n').length;
|
|
217
|
+
results.push({
|
|
218
|
+
pattern: 'hoc',
|
|
219
|
+
componentName: '',
|
|
220
|
+
line,
|
|
221
|
+
column: 1,
|
|
222
|
+
details: {
|
|
223
|
+
hocNames,
|
|
224
|
+
nestingDepth: hocNames.length,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Pattern 2: const EnhancedComponent = withX(Component)
|
|
230
|
+
const constHOCPattern = /const\s+([A-Z][a-zA-Z0-9]*)\s*=\s*((?:with\w+|connect|memo|observer|forwardRef)\s*\([^)]*\))+/g;
|
|
231
|
+
while ((match = constHOCPattern.exec(content)) !== null) {
|
|
232
|
+
const hocChain = match[2] || '';
|
|
233
|
+
const hocNames = extractHOCNames(hocChain);
|
|
234
|
+
if (hocNames.length > 0) {
|
|
235
|
+
const beforeMatch = content.slice(0, match.index);
|
|
236
|
+
const line = beforeMatch.split('\n').length;
|
|
237
|
+
results.push({
|
|
238
|
+
pattern: 'hoc',
|
|
239
|
+
componentName: match[1] || '',
|
|
240
|
+
line,
|
|
241
|
+
column: 1,
|
|
242
|
+
details: {
|
|
243
|
+
hocNames,
|
|
244
|
+
nestingDepth: hocNames.length,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Pattern 3: Function that returns a component (HOC definition)
|
|
250
|
+
const hocDefinitionPattern = /(?:function|const)\s+with([A-Z][a-zA-Z0-9]*)\s*[=(<]/g;
|
|
251
|
+
while ((match = hocDefinitionPattern.exec(content)) !== null) {
|
|
252
|
+
const beforeMatch = content.slice(0, match.index);
|
|
253
|
+
const line = beforeMatch.split('\n').length;
|
|
254
|
+
results.push({
|
|
255
|
+
pattern: 'hoc',
|
|
256
|
+
componentName: `with${match[1]}`,
|
|
257
|
+
line,
|
|
258
|
+
column: 1,
|
|
259
|
+
details: {
|
|
260
|
+
hocNames: [`with${match[1]}`],
|
|
261
|
+
nestingDepth: 1,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return results;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Extract HOC names from a chain like withA(withB(Component))
|
|
269
|
+
*/
|
|
270
|
+
export function extractHOCNames(hocChain) {
|
|
271
|
+
const names = [];
|
|
272
|
+
const pattern = /(with\w+|connect|memo|observer|forwardRef)\s*\(/g;
|
|
273
|
+
let match;
|
|
274
|
+
while ((match = pattern.exec(hocChain)) !== null) {
|
|
275
|
+
if (match[1]) {
|
|
276
|
+
names.push(match[1]);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return names;
|
|
280
|
+
}
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// Helper Functions - Compound Component Detection
|
|
283
|
+
// ============================================================================
|
|
284
|
+
/**
|
|
285
|
+
* Detect compound component pattern
|
|
286
|
+
*/
|
|
287
|
+
export function detectCompoundComponent(content) {
|
|
288
|
+
const results = [];
|
|
289
|
+
// Pattern 1: Component.SubComponent = ...
|
|
290
|
+
const subComponentPattern = /([A-Z][a-zA-Z0-9]*)\.([A-Z][a-zA-Z0-9]*)\s*=/g;
|
|
291
|
+
const subComponents = new Map();
|
|
292
|
+
let match;
|
|
293
|
+
while ((match = subComponentPattern.exec(content)) !== null) {
|
|
294
|
+
const parentName = match[1];
|
|
295
|
+
const subName = match[2];
|
|
296
|
+
if (parentName && subName) {
|
|
297
|
+
if (!subComponents.has(parentName)) {
|
|
298
|
+
subComponents.set(parentName, []);
|
|
299
|
+
}
|
|
300
|
+
subComponents.get(parentName)?.push(subName);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Create results for each compound component
|
|
304
|
+
for (const [parentName, subs] of subComponents) {
|
|
305
|
+
const patternMatch = content.match(new RegExp(`${parentName}\\.${subs[0]}\\s*=`));
|
|
306
|
+
const line = patternMatch
|
|
307
|
+
? content.slice(0, patternMatch.index).split('\n').length
|
|
308
|
+
: 1;
|
|
309
|
+
results.push({
|
|
310
|
+
pattern: 'compound-component',
|
|
311
|
+
componentName: parentName,
|
|
312
|
+
line,
|
|
313
|
+
column: 1,
|
|
314
|
+
details: {
|
|
315
|
+
subComponentNames: subs,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// Pattern 2: Usage of compound components <Parent.Child />
|
|
320
|
+
const usagePattern = /<([A-Z][a-zA-Z0-9]*)\.([A-Z][a-zA-Z0-9]*)/g;
|
|
321
|
+
const usedCompounds = new Set();
|
|
322
|
+
while ((match = usagePattern.exec(content)) !== null) {
|
|
323
|
+
const parentName = match[1];
|
|
324
|
+
if (parentName && !subComponents.has(parentName)) {
|
|
325
|
+
usedCompounds.add(parentName);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return results;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Check if component is part of a compound component pattern
|
|
332
|
+
*/
|
|
333
|
+
export function isCompoundComponentPart(componentName, content) {
|
|
334
|
+
// Check if this component has sub-components attached
|
|
335
|
+
const hasSubComponents = new RegExp(`${componentName}\\.[A-Z][a-zA-Z0-9]*\\s*=`).test(content);
|
|
336
|
+
// Check if this component is used as Parent.Child
|
|
337
|
+
const isUsedAsCompound = new RegExp(`<${componentName}\\.[A-Z]`).test(content);
|
|
338
|
+
return hasSubComponents || isUsedAsCompound;
|
|
339
|
+
}
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Helper Functions - Slot-Based Composition Detection
|
|
342
|
+
// ============================================================================
|
|
343
|
+
/**
|
|
344
|
+
* Detect slot-based composition pattern
|
|
345
|
+
*/
|
|
346
|
+
export function detectSlotBasedComposition(nodeText) {
|
|
347
|
+
const slotNames = [];
|
|
348
|
+
const slotKeywords = ['header', 'footer', 'sidebar', 'content', 'left', 'right', 'top', 'bottom', 'prefix', 'suffix', 'icon', 'label', 'title', 'description', 'actions', 'extra'];
|
|
349
|
+
// Pattern 1: Named slot props in destructuring ({ header, footer, sidebar })
|
|
350
|
+
const destructuringMatch = nodeText.match(/\(\s*\{\s*([^}]+)\s*\}/);
|
|
351
|
+
if (destructuringMatch && destructuringMatch[1]) {
|
|
352
|
+
const propsStr = destructuringMatch[1];
|
|
353
|
+
const props = propsStr.split(',').map(p => p.trim().split(/[=:]/)[0]?.trim() || '');
|
|
354
|
+
for (const prop of props) {
|
|
355
|
+
const propLower = prop.toLowerCase();
|
|
356
|
+
if (slotKeywords.includes(propLower) && !slotNames.includes(propLower)) {
|
|
357
|
+
slotNames.push(propLower);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Pattern 2: Named slot props in JSX (header={...}, footer={...})
|
|
362
|
+
for (const keyword of slotKeywords) {
|
|
363
|
+
const pattern = new RegExp(`\\b${keyword}\\s*[=:]`, 'gi');
|
|
364
|
+
if (pattern.test(nodeText) && !slotNames.includes(keyword)) {
|
|
365
|
+
slotNames.push(keyword);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Pattern 3: Render slot props (renderHeader, renderFooter, etc.)
|
|
369
|
+
const renderSlotPattern = /\b(render[A-Z][a-zA-Z]*)\s*[=:,)]/g;
|
|
370
|
+
let match;
|
|
371
|
+
while ((match = renderSlotPattern.exec(nodeText)) !== null) {
|
|
372
|
+
if (match[1]) {
|
|
373
|
+
const slotName = match[1].replace(/^render/, '').toLowerCase();
|
|
374
|
+
if (!slotNames.includes(slotName)) {
|
|
375
|
+
slotNames.push(slotName);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Pattern 4: Render slot props in destructuring ({ renderHeader, renderFooter })
|
|
380
|
+
if (destructuringMatch && destructuringMatch[1]) {
|
|
381
|
+
const propsStr = destructuringMatch[1];
|
|
382
|
+
const renderPropMatches = propsStr.matchAll(/\b(render[A-Z][a-zA-Z]*)\b/g);
|
|
383
|
+
for (const renderMatch of renderPropMatches) {
|
|
384
|
+
if (renderMatch[1]) {
|
|
385
|
+
const slotName = renderMatch[1].replace(/^render/, '').toLowerCase();
|
|
386
|
+
if (!slotNames.includes(slotName)) {
|
|
387
|
+
slotNames.push(slotName);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Pattern 5: Slot component usage (<Slot name="..." />)
|
|
393
|
+
const slotComponentPattern = /<Slot\s+name\s*=\s*["']([^"']+)["']/g;
|
|
394
|
+
while ((match = slotComponentPattern.exec(nodeText)) !== null) {
|
|
395
|
+
if (match[1] && !slotNames.includes(match[1])) {
|
|
396
|
+
slotNames.push(match[1]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (slotNames.length >= 2) {
|
|
400
|
+
return {
|
|
401
|
+
pattern: 'slot-based',
|
|
402
|
+
componentName: '',
|
|
403
|
+
line: 1,
|
|
404
|
+
column: 1,
|
|
405
|
+
details: {
|
|
406
|
+
slotNames,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Helper Functions - Provider/Consumer Detection
|
|
414
|
+
// ============================================================================
|
|
415
|
+
/**
|
|
416
|
+
* Detect Provider/Consumer pattern
|
|
417
|
+
*/
|
|
418
|
+
export function detectProviderConsumer(content) {
|
|
419
|
+
const results = [];
|
|
420
|
+
// Pattern 1: Context.Provider usage
|
|
421
|
+
const providerPattern = /<([A-Z][a-zA-Z0-9]*(?:Context)?)\s*\.\s*Provider/g;
|
|
422
|
+
let match;
|
|
423
|
+
while ((match = providerPattern.exec(content)) !== null) {
|
|
424
|
+
const contextName = match[1];
|
|
425
|
+
if (!contextName)
|
|
426
|
+
continue;
|
|
427
|
+
const beforeMatch = content.slice(0, match.index);
|
|
428
|
+
const line = beforeMatch.split('\n').length;
|
|
429
|
+
results.push({
|
|
430
|
+
pattern: 'provider-consumer',
|
|
431
|
+
componentName: '',
|
|
432
|
+
line,
|
|
433
|
+
column: 1,
|
|
434
|
+
details: {
|
|
435
|
+
contextName,
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
// Pattern 2: Context.Consumer usage
|
|
440
|
+
const consumerPattern = /<([A-Z][a-zA-Z0-9]*(?:Context)?)\s*\.\s*Consumer/g;
|
|
441
|
+
while ((match = consumerPattern.exec(content)) !== null) {
|
|
442
|
+
const contextName = match[1];
|
|
443
|
+
if (!contextName)
|
|
444
|
+
continue;
|
|
445
|
+
const beforeMatch = content.slice(0, match.index);
|
|
446
|
+
const line = beforeMatch.split('\n').length;
|
|
447
|
+
// Check if we already have this context
|
|
448
|
+
const existing = results.find(r => r.details.contextName === contextName);
|
|
449
|
+
if (!existing) {
|
|
450
|
+
results.push({
|
|
451
|
+
pattern: 'provider-consumer',
|
|
452
|
+
componentName: '',
|
|
453
|
+
line,
|
|
454
|
+
column: 1,
|
|
455
|
+
details: {
|
|
456
|
+
contextName,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Pattern 3: createContext usage
|
|
462
|
+
const createContextPattern = /const\s+([A-Z][a-zA-Z0-9]*(?:Context)?)\s*=\s*(?:React\.)?createContext/g;
|
|
463
|
+
while ((match = createContextPattern.exec(content)) !== null) {
|
|
464
|
+
const contextName = match[1];
|
|
465
|
+
if (!contextName)
|
|
466
|
+
continue;
|
|
467
|
+
const beforeMatch = content.slice(0, match.index);
|
|
468
|
+
const line = beforeMatch.split('\n').length;
|
|
469
|
+
results.push({
|
|
470
|
+
pattern: 'provider-consumer',
|
|
471
|
+
componentName: '',
|
|
472
|
+
line,
|
|
473
|
+
column: 1,
|
|
474
|
+
details: {
|
|
475
|
+
contextName,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return results;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Count nested providers in content
|
|
483
|
+
*/
|
|
484
|
+
export function countNestedProviders(content) {
|
|
485
|
+
const providerMatches = content.match(/<[A-Z][a-zA-Z0-9]*(?:Context)?\s*\.\s*Provider/g);
|
|
486
|
+
return providerMatches ? providerMatches.length : 0;
|
|
487
|
+
}
|
|
488
|
+
// ============================================================================
|
|
489
|
+
// Helper Functions - Controlled/Uncontrolled Detection
|
|
490
|
+
// ============================================================================
|
|
491
|
+
/**
|
|
492
|
+
* Detect controlled component pattern
|
|
493
|
+
*/
|
|
494
|
+
export function detectControlledPattern(nodeText) {
|
|
495
|
+
const controlledProps = [];
|
|
496
|
+
for (const pattern of CONTROLLED_PROP_PATTERNS) {
|
|
497
|
+
const valuePattern = new RegExp(`\\b${pattern.value}\\s*[=:]`, 'g');
|
|
498
|
+
const onChangePattern = new RegExp(`\\b${pattern.onChange}\\s*[=:]`, 'g');
|
|
499
|
+
if (valuePattern.test(nodeText) && onChangePattern.test(nodeText)) {
|
|
500
|
+
controlledProps.push(pattern.value);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (controlledProps.length > 0) {
|
|
504
|
+
return {
|
|
505
|
+
pattern: 'controlled',
|
|
506
|
+
componentName: '',
|
|
507
|
+
line: 1,
|
|
508
|
+
column: 1,
|
|
509
|
+
details: {
|
|
510
|
+
controlledProps,
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Detect uncontrolled component pattern
|
|
518
|
+
*/
|
|
519
|
+
export function detectUncontrolledPattern(nodeText) {
|
|
520
|
+
const uncontrolledProps = [];
|
|
521
|
+
for (const propName of UNCONTROLLED_PROP_PATTERNS) {
|
|
522
|
+
const pattern = new RegExp(`\\b${propName}\\s*[=:]`, 'g');
|
|
523
|
+
if (pattern.test(nodeText)) {
|
|
524
|
+
uncontrolledProps.push(propName);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// Also check for ref usage without value prop (uncontrolled form elements)
|
|
528
|
+
const hasRef = /\bref\s*[=:]/.test(nodeText);
|
|
529
|
+
const hasValue = /\bvalue\s*[=:]/.test(nodeText);
|
|
530
|
+
if (hasRef && !hasValue) {
|
|
531
|
+
uncontrolledProps.push('ref');
|
|
532
|
+
}
|
|
533
|
+
if (uncontrolledProps.length > 0) {
|
|
534
|
+
return {
|
|
535
|
+
pattern: 'uncontrolled',
|
|
536
|
+
componentName: '',
|
|
537
|
+
line: 1,
|
|
538
|
+
column: 1,
|
|
539
|
+
details: {
|
|
540
|
+
controlledProps: uncontrolledProps,
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Check if component mixes controlled and uncontrolled patterns
|
|
548
|
+
*/
|
|
549
|
+
export function hasMixedControlledPatterns(nodeText) {
|
|
550
|
+
const hasControlled = detectControlledPattern(nodeText) !== null;
|
|
551
|
+
const hasUncontrolled = detectUncontrolledPattern(nodeText) !== null;
|
|
552
|
+
return hasControlled && hasUncontrolled;
|
|
553
|
+
}
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Helper Functions - Anti-Pattern Detection
|
|
556
|
+
// ============================================================================
|
|
557
|
+
/**
|
|
558
|
+
* Detect composition anti-patterns
|
|
559
|
+
*/
|
|
560
|
+
export function detectAntiPatterns(nodeText, content, config) {
|
|
561
|
+
const antiPatterns = [];
|
|
562
|
+
// Check for deeply nested HOCs
|
|
563
|
+
const hocUsages = detectHOC(content);
|
|
564
|
+
for (const hoc of hocUsages) {
|
|
565
|
+
if (hoc.details.nestingDepth && hoc.details.nestingDepth > config.maxHOCDepth) {
|
|
566
|
+
antiPatterns.push({
|
|
567
|
+
type: 'deeply-nested-hocs',
|
|
568
|
+
description: `Component has ${hoc.details.nestingDepth} nested HOCs (max: ${config.maxHOCDepth})`,
|
|
569
|
+
severity: 'warning',
|
|
570
|
+
suggestion: 'Consider using hooks or composition instead of deeply nested HOCs',
|
|
571
|
+
line: hoc.line,
|
|
572
|
+
column: 1,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Check for overuse of render props
|
|
577
|
+
const renderProps = detectRenderProps(nodeText);
|
|
578
|
+
if (renderProps && renderProps.details.renderPropNames) {
|
|
579
|
+
const count = renderProps.details.renderPropNames.length;
|
|
580
|
+
if (count > config.maxRenderProps) {
|
|
581
|
+
antiPatterns.push({
|
|
582
|
+
type: 'overuse-render-props',
|
|
583
|
+
description: `Component has ${count} render props (max: ${config.maxRenderProps})`,
|
|
584
|
+
severity: 'info',
|
|
585
|
+
suggestion: 'Consider splitting into smaller components or using composition',
|
|
586
|
+
line: renderProps.line,
|
|
587
|
+
column: 1,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// Check for excessive context nesting
|
|
592
|
+
const providerCount = countNestedProviders(content);
|
|
593
|
+
if (providerCount > config.maxContextNesting) {
|
|
594
|
+
antiPatterns.push({
|
|
595
|
+
type: 'excessive-context',
|
|
596
|
+
description: `File has ${providerCount} nested context providers (max: ${config.maxContextNesting})`,
|
|
597
|
+
severity: 'warning',
|
|
598
|
+
suggestion: 'Consider combining related contexts or using a state management library',
|
|
599
|
+
line: 1,
|
|
600
|
+
column: 1,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
// Check for mixed controlled/uncontrolled patterns
|
|
604
|
+
if (config.detectControlledPatterns && hasMixedControlledPatterns(nodeText)) {
|
|
605
|
+
antiPatterns.push({
|
|
606
|
+
type: 'mixed-controlled',
|
|
607
|
+
description: 'Component mixes controlled and uncontrolled patterns',
|
|
608
|
+
severity: 'warning',
|
|
609
|
+
suggestion: 'Use either controlled or uncontrolled pattern consistently',
|
|
610
|
+
line: 1,
|
|
611
|
+
column: 1,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
// Check for missing children prop when component renders children
|
|
615
|
+
const rendersChildElements = /<[A-Z][a-zA-Z0-9]*[^>]*>[^<]+<\/[A-Z]/.test(nodeText);
|
|
616
|
+
const acceptsChildren = acceptsChildrenProp(nodeText);
|
|
617
|
+
if (rendersChildElements && !acceptsChildren) {
|
|
618
|
+
// This might indicate missing children prop
|
|
619
|
+
// Only flag if component seems to be a wrapper
|
|
620
|
+
const isWrapper = /return\s*\(\s*<[^>]+>\s*\{/.test(nodeText);
|
|
621
|
+
if (isWrapper) {
|
|
622
|
+
antiPatterns.push({
|
|
623
|
+
type: 'missing-children',
|
|
624
|
+
description: 'Component appears to be a wrapper but does not accept children prop',
|
|
625
|
+
severity: 'info',
|
|
626
|
+
suggestion: 'Consider adding children prop to allow composition',
|
|
627
|
+
line: 1,
|
|
628
|
+
column: 1,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return antiPatterns;
|
|
633
|
+
}
|
|
634
|
+
// ============================================================================
|
|
635
|
+
// Helper Functions - Analysis
|
|
636
|
+
// ============================================================================
|
|
637
|
+
/**
|
|
638
|
+
* Analyze a single component's composition patterns
|
|
639
|
+
*/
|
|
640
|
+
export function analyzeComponentComposition(node, content, filePath, config) {
|
|
641
|
+
const componentName = getComponentName(node, content);
|
|
642
|
+
if (!componentName) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
const nodeText = node.text;
|
|
646
|
+
const patterns = [];
|
|
647
|
+
// Detect children prop usage
|
|
648
|
+
const childrenUsage = detectChildrenProp(nodeText);
|
|
649
|
+
if (childrenUsage) {
|
|
650
|
+
childrenUsage.componentName = componentName;
|
|
651
|
+
patterns.push(childrenUsage);
|
|
652
|
+
}
|
|
653
|
+
// Detect render props
|
|
654
|
+
const renderPropsUsage = detectRenderProps(nodeText);
|
|
655
|
+
if (renderPropsUsage) {
|
|
656
|
+
renderPropsUsage.componentName = componentName;
|
|
657
|
+
patterns.push(renderPropsUsage);
|
|
658
|
+
}
|
|
659
|
+
// Detect slot-based composition
|
|
660
|
+
const slotUsage = detectSlotBasedComposition(nodeText);
|
|
661
|
+
if (slotUsage) {
|
|
662
|
+
slotUsage.componentName = componentName;
|
|
663
|
+
patterns.push(slotUsage);
|
|
664
|
+
}
|
|
665
|
+
// Detect controlled pattern
|
|
666
|
+
if (config.detectControlledPatterns) {
|
|
667
|
+
const controlledUsage = detectControlledPattern(nodeText);
|
|
668
|
+
if (controlledUsage) {
|
|
669
|
+
controlledUsage.componentName = componentName;
|
|
670
|
+
patterns.push(controlledUsage);
|
|
671
|
+
}
|
|
672
|
+
const uncontrolledUsage = detectUncontrolledPattern(nodeText);
|
|
673
|
+
if (uncontrolledUsage) {
|
|
674
|
+
uncontrolledUsage.componentName = componentName;
|
|
675
|
+
patterns.push(uncontrolledUsage);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Detect HOCs (from full content, not just node)
|
|
679
|
+
const hocUsages = detectHOC(content);
|
|
680
|
+
for (const hoc of hocUsages) {
|
|
681
|
+
if (hoc.componentName === componentName || hoc.componentName === '') {
|
|
682
|
+
hoc.componentName = componentName;
|
|
683
|
+
patterns.push(hoc);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// Detect compound components
|
|
687
|
+
const compoundUsages = detectCompoundComponent(content);
|
|
688
|
+
for (const compound of compoundUsages) {
|
|
689
|
+
if (compound.componentName === componentName) {
|
|
690
|
+
patterns.push(compound);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// Detect provider/consumer
|
|
694
|
+
const providerUsages = detectProviderConsumer(nodeText);
|
|
695
|
+
for (const provider of providerUsages) {
|
|
696
|
+
provider.componentName = componentName;
|
|
697
|
+
patterns.push(provider);
|
|
698
|
+
}
|
|
699
|
+
// Detect anti-patterns
|
|
700
|
+
const antiPatterns = detectAntiPatterns(nodeText, content, config);
|
|
701
|
+
return {
|
|
702
|
+
componentName,
|
|
703
|
+
filePath,
|
|
704
|
+
line: node.startPosition.row + 1,
|
|
705
|
+
column: node.startPosition.column + 1,
|
|
706
|
+
patterns,
|
|
707
|
+
antiPatterns,
|
|
708
|
+
acceptsChildren: acceptsChildrenProp(nodeText),
|
|
709
|
+
usesRenderProps: renderPropsUsage !== null,
|
|
710
|
+
isHOC: hocUsages.some(h => h.componentName === componentName),
|
|
711
|
+
isCompoundComponent: isCompoundComponentPart(componentName, content),
|
|
712
|
+
isControlled: detectControlledPattern(nodeText) !== null,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Find dominant pattern from a list
|
|
717
|
+
*/
|
|
718
|
+
function findDominantPattern(patterns) {
|
|
719
|
+
const counts = new Map();
|
|
720
|
+
for (const pattern of patterns) {
|
|
721
|
+
if (pattern !== 'none') {
|
|
722
|
+
counts.set(pattern, (counts.get(pattern) || 0) + 1);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
let dominant = 'none';
|
|
726
|
+
let maxCount = 0;
|
|
727
|
+
for (const [pattern, count] of counts) {
|
|
728
|
+
if (count > maxCount) {
|
|
729
|
+
maxCount = count;
|
|
730
|
+
dominant = pattern;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const total = patterns.filter(p => p !== 'none').length;
|
|
734
|
+
const confidence = total > 0 ? maxCount / total : 0;
|
|
735
|
+
return { dominant, confidence };
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Analyze composition patterns across multiple components
|
|
739
|
+
*/
|
|
740
|
+
export function analyzeCompositionPatterns(components) {
|
|
741
|
+
if (components.length === 0) {
|
|
742
|
+
return {
|
|
743
|
+
components: [],
|
|
744
|
+
dominantPattern: 'none',
|
|
745
|
+
patternCounts: {
|
|
746
|
+
'children-prop': 0,
|
|
747
|
+
'render-props': 0,
|
|
748
|
+
'hoc': 0,
|
|
749
|
+
'compound-component': 0,
|
|
750
|
+
'slot-based': 0,
|
|
751
|
+
'provider-consumer': 0,
|
|
752
|
+
'controlled': 0,
|
|
753
|
+
'uncontrolled': 0,
|
|
754
|
+
'forwarded-ref': 0,
|
|
755
|
+
'none': 0,
|
|
756
|
+
},
|
|
757
|
+
confidence: 0,
|
|
758
|
+
componentsWithAntiPatterns: [],
|
|
759
|
+
healthScore: 1.0,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
// Collect all patterns
|
|
763
|
+
const allPatterns = [];
|
|
764
|
+
const patternCounts = {
|
|
765
|
+
'children-prop': 0,
|
|
766
|
+
'render-props': 0,
|
|
767
|
+
'hoc': 0,
|
|
768
|
+
'compound-component': 0,
|
|
769
|
+
'slot-based': 0,
|
|
770
|
+
'provider-consumer': 0,
|
|
771
|
+
'controlled': 0,
|
|
772
|
+
'uncontrolled': 0,
|
|
773
|
+
'forwarded-ref': 0,
|
|
774
|
+
'none': 0,
|
|
775
|
+
};
|
|
776
|
+
for (const comp of components) {
|
|
777
|
+
for (const pattern of comp.patterns) {
|
|
778
|
+
allPatterns.push(pattern.pattern);
|
|
779
|
+
patternCounts[pattern.pattern]++;
|
|
780
|
+
}
|
|
781
|
+
if (comp.patterns.length === 0) {
|
|
782
|
+
patternCounts['none']++;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// Find dominant pattern
|
|
786
|
+
const { dominant, confidence } = findDominantPattern(allPatterns);
|
|
787
|
+
// Find components with anti-patterns
|
|
788
|
+
const componentsWithAntiPatterns = components.filter(c => c.antiPatterns.length > 0);
|
|
789
|
+
// Calculate health score
|
|
790
|
+
const totalAntiPatterns = components.reduce((sum, c) => sum + c.antiPatterns.length, 0);
|
|
791
|
+
const healthScore = Math.max(0, 1 - (totalAntiPatterns / (components.length * 2)));
|
|
792
|
+
return {
|
|
793
|
+
components,
|
|
794
|
+
dominantPattern: dominant,
|
|
795
|
+
patternCounts,
|
|
796
|
+
confidence,
|
|
797
|
+
componentsWithAntiPatterns,
|
|
798
|
+
healthScore,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
// ============================================================================
|
|
802
|
+
// Pattern Description Helpers
|
|
803
|
+
// ============================================================================
|
|
804
|
+
const ANTI_PATTERN_DESCRIPTIONS = {
|
|
805
|
+
'deeply-nested-hocs': 'deeply nested HOCs',
|
|
806
|
+
'prop-drilling': 'prop drilling',
|
|
807
|
+
'missing-children': 'missing children prop',
|
|
808
|
+
'overuse-render-props': 'overuse of render props',
|
|
809
|
+
'mixed-controlled': 'mixed controlled/uncontrolled patterns',
|
|
810
|
+
'excessive-context': 'excessive context nesting',
|
|
811
|
+
};
|
|
812
|
+
// ============================================================================
|
|
813
|
+
// Composition Detector Class
|
|
814
|
+
// ============================================================================
|
|
815
|
+
/**
|
|
816
|
+
* Detector for component composition patterns
|
|
817
|
+
*
|
|
818
|
+
* Identifies composition patterns including children prop usage, render props,
|
|
819
|
+
* HOCs, compound components, and more. Reports anti-patterns and suggests
|
|
820
|
+
* improvements.
|
|
821
|
+
*
|
|
822
|
+
* @requirements 8.6 - THE Component_Detector SHALL detect component composition patterns
|
|
823
|
+
*/
|
|
824
|
+
export class CompositionDetector extends ASTDetector {
|
|
825
|
+
id = 'components/composition';
|
|
826
|
+
category = 'components';
|
|
827
|
+
subcategory = 'composition';
|
|
828
|
+
name = 'Composition Pattern Detector';
|
|
829
|
+
description = 'Detects component composition patterns (children, render props, HOCs, compound components) and identifies anti-patterns';
|
|
830
|
+
supportedLanguages = ['typescript', 'javascript'];
|
|
831
|
+
config;
|
|
832
|
+
constructor(config = {}) {
|
|
833
|
+
super();
|
|
834
|
+
this.config = { ...DEFAULT_COMPOSITION_CONFIG, ...config };
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Detect composition patterns in the project
|
|
838
|
+
*/
|
|
839
|
+
async detect(context) {
|
|
840
|
+
const patterns = [];
|
|
841
|
+
const violations = [];
|
|
842
|
+
// Find all components in the file
|
|
843
|
+
const componentInfos = this.findComponentsInFile(context);
|
|
844
|
+
if (componentInfos.length === 0) {
|
|
845
|
+
return this.createEmptyResult();
|
|
846
|
+
}
|
|
847
|
+
// Analyze patterns across all components
|
|
848
|
+
const analysis = analyzeCompositionPatterns(componentInfos);
|
|
849
|
+
// Create pattern matches for detected patterns
|
|
850
|
+
for (const [patternType, count] of Object.entries(analysis.patternCounts)) {
|
|
851
|
+
if (count > 0 && patternType !== 'none') {
|
|
852
|
+
patterns.push(this.createPatternMatch(context.file, patternType, count, analysis));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Generate violations for anti-patterns
|
|
856
|
+
for (const comp of analysis.componentsWithAntiPatterns) {
|
|
857
|
+
if (comp.filePath === context.file) {
|
|
858
|
+
for (const antiPattern of comp.antiPatterns) {
|
|
859
|
+
violations.push(this.createAntiPatternViolation(context.file, comp, antiPattern));
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return this.createResult(patterns, violations, analysis.confidence);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Find all React components in a file
|
|
867
|
+
*/
|
|
868
|
+
findComponentsInFile(context) {
|
|
869
|
+
const components = [];
|
|
870
|
+
if (context.ast) {
|
|
871
|
+
// Use AST to find components
|
|
872
|
+
const functionNodes = this.findNodesByTypes(context.ast, [
|
|
873
|
+
'function_declaration',
|
|
874
|
+
'arrow_function',
|
|
875
|
+
'function_expression',
|
|
876
|
+
]);
|
|
877
|
+
for (const node of functionNodes) {
|
|
878
|
+
if (isReactComponent(node, context.content)) {
|
|
879
|
+
const info = analyzeComponentComposition(node, context.content, context.file, this.config);
|
|
880
|
+
if (info) {
|
|
881
|
+
components.push(info);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
// Fallback: use regex-based detection
|
|
888
|
+
const componentMatches = this.findComponentsWithRegex(context.content, context.file);
|
|
889
|
+
components.push(...componentMatches);
|
|
890
|
+
}
|
|
891
|
+
return components;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Find components using regex (fallback when AST is not available)
|
|
895
|
+
*/
|
|
896
|
+
findComponentsWithRegex(content, filePath) {
|
|
897
|
+
const components = [];
|
|
898
|
+
// Pattern for arrow function components
|
|
899
|
+
const arrowPattern = /(?:export\s+)?const\s+([A-Z][a-zA-Z0-9]*)\s*(?::\s*(?:React\.)?(?:FC|FunctionComponent)\s*<[^>]*>\s*)?=\s*\(/g;
|
|
900
|
+
// Pattern for function declaration components
|
|
901
|
+
const functionPattern = /(?:export\s+)?function\s+([A-Z][a-zA-Z0-9]*)\s*\(/g;
|
|
902
|
+
const processMatch = (match, componentName) => {
|
|
903
|
+
const beforeMatch = content.slice(0, match.index);
|
|
904
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
905
|
+
// Get component body (simplified)
|
|
906
|
+
const startIndex = match.index;
|
|
907
|
+
let braceCount = 0;
|
|
908
|
+
let endIndex = startIndex;
|
|
909
|
+
let foundStart = false;
|
|
910
|
+
for (let i = startIndex; i < content.length && i < startIndex + 10000; i++) {
|
|
911
|
+
const char = content[i];
|
|
912
|
+
if (char === '{') {
|
|
913
|
+
braceCount++;
|
|
914
|
+
foundStart = true;
|
|
915
|
+
}
|
|
916
|
+
else if (char === '}') {
|
|
917
|
+
braceCount--;
|
|
918
|
+
if (foundStart && braceCount === 0) {
|
|
919
|
+
endIndex = i + 1;
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const componentBody = content.slice(startIndex, endIndex);
|
|
925
|
+
// Check if it's a React component (contains JSX)
|
|
926
|
+
if (!componentBody.includes('<') || (!componentBody.includes('/>') && !componentBody.includes('</'))) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const patterns = [];
|
|
930
|
+
// Detect patterns
|
|
931
|
+
const childrenUsage = detectChildrenProp(componentBody);
|
|
932
|
+
if (childrenUsage) {
|
|
933
|
+
childrenUsage.componentName = componentName;
|
|
934
|
+
patterns.push(childrenUsage);
|
|
935
|
+
}
|
|
936
|
+
const renderPropsUsage = detectRenderProps(componentBody);
|
|
937
|
+
if (renderPropsUsage) {
|
|
938
|
+
renderPropsUsage.componentName = componentName;
|
|
939
|
+
patterns.push(renderPropsUsage);
|
|
940
|
+
}
|
|
941
|
+
const slotUsage = detectSlotBasedComposition(componentBody);
|
|
942
|
+
if (slotUsage) {
|
|
943
|
+
slotUsage.componentName = componentName;
|
|
944
|
+
patterns.push(slotUsage);
|
|
945
|
+
}
|
|
946
|
+
if (this.config.detectControlledPatterns) {
|
|
947
|
+
const controlledUsage = detectControlledPattern(componentBody);
|
|
948
|
+
if (controlledUsage) {
|
|
949
|
+
controlledUsage.componentName = componentName;
|
|
950
|
+
patterns.push(controlledUsage);
|
|
951
|
+
}
|
|
952
|
+
const uncontrolledUsage = detectUncontrolledPattern(componentBody);
|
|
953
|
+
if (uncontrolledUsage) {
|
|
954
|
+
uncontrolledUsage.componentName = componentName;
|
|
955
|
+
patterns.push(uncontrolledUsage);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
// Detect anti-patterns
|
|
959
|
+
const antiPatterns = detectAntiPatterns(componentBody, content, this.config);
|
|
960
|
+
components.push({
|
|
961
|
+
componentName,
|
|
962
|
+
filePath,
|
|
963
|
+
line: lineNumber,
|
|
964
|
+
column: 1,
|
|
965
|
+
patterns,
|
|
966
|
+
antiPatterns,
|
|
967
|
+
acceptsChildren: acceptsChildrenProp(componentBody),
|
|
968
|
+
usesRenderProps: renderPropsUsage !== null,
|
|
969
|
+
isHOC: false,
|
|
970
|
+
isCompoundComponent: isCompoundComponentPart(componentName, content),
|
|
971
|
+
isControlled: detectControlledPattern(componentBody) !== null,
|
|
972
|
+
});
|
|
973
|
+
};
|
|
974
|
+
let match;
|
|
975
|
+
while ((match = arrowPattern.exec(content)) !== null) {
|
|
976
|
+
if (match[1]) {
|
|
977
|
+
processMatch(match, match[1]);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
while ((match = functionPattern.exec(content)) !== null) {
|
|
981
|
+
if (match[1]) {
|
|
982
|
+
processMatch(match, match[1]);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Also detect HOCs and compound components at file level
|
|
986
|
+
const hocUsages = detectHOC(content);
|
|
987
|
+
for (const hoc of hocUsages) {
|
|
988
|
+
const existing = components.find(c => c.componentName === hoc.componentName);
|
|
989
|
+
if (existing) {
|
|
990
|
+
existing.patterns.push(hoc);
|
|
991
|
+
existing.isHOC = true;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const compoundUsages = detectCompoundComponent(content);
|
|
995
|
+
for (const compound of compoundUsages) {
|
|
996
|
+
const existing = components.find(c => c.componentName === compound.componentName);
|
|
997
|
+
if (existing) {
|
|
998
|
+
existing.patterns.push(compound);
|
|
999
|
+
existing.isCompoundComponent = true;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const providerUsages = detectProviderConsumer(content);
|
|
1003
|
+
for (const provider of providerUsages) {
|
|
1004
|
+
// Add to first component or create a placeholder
|
|
1005
|
+
if (components.length > 0 && components[0]) {
|
|
1006
|
+
components[0].patterns.push(provider);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return components;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Create a pattern match for a composition pattern
|
|
1013
|
+
*/
|
|
1014
|
+
createPatternMatch(file, patternType, count, analysis) {
|
|
1015
|
+
const total = Object.values(analysis.patternCounts).reduce((a, b) => a + b, 0) - analysis.patternCounts['none'];
|
|
1016
|
+
const confidence = total > 0 ? count / total : 0;
|
|
1017
|
+
return {
|
|
1018
|
+
patternId: `composition-${patternType}`,
|
|
1019
|
+
location: { file, line: 1, column: 1 },
|
|
1020
|
+
confidence,
|
|
1021
|
+
isOutlier: confidence < 0.2,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Create a violation for an anti-pattern
|
|
1026
|
+
*/
|
|
1027
|
+
createAntiPatternViolation(file, component, antiPattern) {
|
|
1028
|
+
const range = {
|
|
1029
|
+
start: { line: antiPattern.line, character: antiPattern.column },
|
|
1030
|
+
end: { line: antiPattern.line, character: antiPattern.column + 1 },
|
|
1031
|
+
};
|
|
1032
|
+
return {
|
|
1033
|
+
id: `composition-${component.componentName}-${antiPattern.type}-${file.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
1034
|
+
patternId: 'components/composition',
|
|
1035
|
+
severity: antiPattern.severity,
|
|
1036
|
+
file,
|
|
1037
|
+
range,
|
|
1038
|
+
message: `${component.componentName}: ${antiPattern.description}`,
|
|
1039
|
+
expected: 'Clean composition pattern',
|
|
1040
|
+
actual: ANTI_PATTERN_DESCRIPTIONS[antiPattern.type],
|
|
1041
|
+
explanation: antiPattern.suggestion,
|
|
1042
|
+
aiExplainAvailable: true,
|
|
1043
|
+
aiFixAvailable: antiPattern.type !== 'deeply-nested-hocs',
|
|
1044
|
+
firstSeen: new Date(),
|
|
1045
|
+
occurrences: 1,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Generate a quick fix for composition violations
|
|
1050
|
+
*/
|
|
1051
|
+
generateQuickFix(violation) {
|
|
1052
|
+
// Extract anti-pattern type from violation
|
|
1053
|
+
// ID format: composition-ComponentName-antiPatternType-filePath
|
|
1054
|
+
// antiPatternType can contain hyphens (e.g., missing-children, mixed-controlled)
|
|
1055
|
+
const antiPatternMatch = violation.id.match(/composition-[A-Za-z0-9]+-([a-z]+-[a-z]+|[a-z]+)-/);
|
|
1056
|
+
if (!antiPatternMatch || !antiPatternMatch[1]) {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
const antiPatternType = antiPatternMatch[1];
|
|
1060
|
+
switch (antiPatternType) {
|
|
1061
|
+
case 'missing-children':
|
|
1062
|
+
return {
|
|
1063
|
+
title: 'Add children prop',
|
|
1064
|
+
kind: 'quickfix',
|
|
1065
|
+
edit: {
|
|
1066
|
+
changes: {},
|
|
1067
|
+
documentChanges: [],
|
|
1068
|
+
},
|
|
1069
|
+
isPreferred: true,
|
|
1070
|
+
confidence: 0.8,
|
|
1071
|
+
preview: 'Add children: React.ReactNode to props type',
|
|
1072
|
+
};
|
|
1073
|
+
case 'mixed-controlled':
|
|
1074
|
+
return {
|
|
1075
|
+
title: 'Convert to controlled component',
|
|
1076
|
+
kind: 'refactor',
|
|
1077
|
+
edit: {
|
|
1078
|
+
changes: {},
|
|
1079
|
+
documentChanges: [],
|
|
1080
|
+
},
|
|
1081
|
+
isPreferred: true,
|
|
1082
|
+
confidence: 0.7,
|
|
1083
|
+
preview: 'Remove default* props and use value/onChange pattern',
|
|
1084
|
+
};
|
|
1085
|
+
case 'overuse-render-props':
|
|
1086
|
+
return {
|
|
1087
|
+
title: 'Extract render props to separate components',
|
|
1088
|
+
kind: 'refactor',
|
|
1089
|
+
edit: {
|
|
1090
|
+
changes: {},
|
|
1091
|
+
documentChanges: [],
|
|
1092
|
+
},
|
|
1093
|
+
isPreferred: false,
|
|
1094
|
+
confidence: 0.6,
|
|
1095
|
+
preview: 'Split component into smaller, focused components',
|
|
1096
|
+
};
|
|
1097
|
+
case 'excessive-context':
|
|
1098
|
+
return {
|
|
1099
|
+
title: 'Combine related contexts',
|
|
1100
|
+
kind: 'refactor',
|
|
1101
|
+
edit: {
|
|
1102
|
+
changes: {},
|
|
1103
|
+
documentChanges: [],
|
|
1104
|
+
},
|
|
1105
|
+
isPreferred: false,
|
|
1106
|
+
confidence: 0.5,
|
|
1107
|
+
preview: 'Merge related context providers into a single provider',
|
|
1108
|
+
};
|
|
1109
|
+
default:
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
// ============================================================================
|
|
1115
|
+
// Factory Function
|
|
1116
|
+
// ============================================================================
|
|
1117
|
+
/**
|
|
1118
|
+
* Create a new CompositionDetector instance
|
|
1119
|
+
*/
|
|
1120
|
+
export function createCompositionDetector(config = {}) {
|
|
1121
|
+
return new CompositionDetector(config);
|
|
1122
|
+
}
|
|
1123
|
+
//# sourceMappingURL=composition.js.map
|